From b3cc35e354c437fbd572618513a2970d3e885e5f Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 2 May 2021 14:28:42 +0300 Subject: [PATCH 01/56] Refactoring --- Server/Extensions/ServiceExtension.cs | 20 +++++++++ .../{ => Interfaces}/IRoomCoordinator.cs | 0 .../{ => Interfaces}/IRoundCoordinator.cs | 0 .../{Impl => }/RoomCoordinator.cs | 0 .../{Impl => }/RoundCoordinator.cs | 0 .../Models/{ => Interfaces}/IRoom.cs | 0 .../Models/{ => Interfaces}/IRound.cs | 0 Server/GameLogic/Models/{Impl => }/Room.cs | 0 Server/GameLogic/Models/{Impl => }/Round.cs | 0 Server/Startup.cs | 41 ++----------------- 10 files changed, 24 insertions(+), 37 deletions(-) create mode 100644 Server/Extensions/ServiceExtension.cs rename Server/GameLogic/LogicServices/{ => Interfaces}/IRoomCoordinator.cs (100%) rename Server/GameLogic/LogicServices/{ => Interfaces}/IRoundCoordinator.cs (100%) rename Server/GameLogic/LogicServices/{Impl => }/RoomCoordinator.cs (100%) rename Server/GameLogic/LogicServices/{Impl => }/RoundCoordinator.cs (100%) rename Server/GameLogic/Models/{ => Interfaces}/IRoom.cs (100%) rename Server/GameLogic/Models/{ => Interfaces}/IRound.cs (100%) rename Server/GameLogic/Models/{Impl => }/Room.cs (100%) rename Server/GameLogic/Models/{Impl => }/Round.cs (100%) diff --git a/Server/Extensions/ServiceExtension.cs b/Server/Extensions/ServiceExtension.cs new file mode 100644 index 0000000..bd3b90c --- /dev/null +++ b/Server/Extensions/ServiceExtension.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Server.GameLogic.LogicServices; +using Server.GameLogic.LogicServices.Impl; +using Server.Services; +using Server.Services.Interfaces; + +namespace Server.Extensions +{ + public static class ServiceExtension + { + public static IServiceCollection AddServices(this IServiceCollection service) + { + return service.AddSingleton(typeof(IDeserializedObject<>), typeof(DeserializedObject<>)) + .AddTransient(typeof(IStorage<>), typeof(Storage<>)) + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + } +} \ No newline at end of file diff --git a/Server/GameLogic/LogicServices/IRoomCoordinator.cs b/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/IRoomCoordinator.cs rename to Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs diff --git a/Server/GameLogic/LogicServices/IRoundCoordinator.cs b/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/IRoundCoordinator.cs rename to Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs diff --git a/Server/GameLogic/LogicServices/Impl/RoomCoordinator.cs b/Server/GameLogic/LogicServices/RoomCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/Impl/RoomCoordinator.cs rename to Server/GameLogic/LogicServices/RoomCoordinator.cs diff --git a/Server/GameLogic/LogicServices/Impl/RoundCoordinator.cs b/Server/GameLogic/LogicServices/RoundCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/Impl/RoundCoordinator.cs rename to Server/GameLogic/LogicServices/RoundCoordinator.cs diff --git a/Server/GameLogic/Models/IRoom.cs b/Server/GameLogic/Models/Interfaces/IRoom.cs similarity index 100% rename from Server/GameLogic/Models/IRoom.cs rename to Server/GameLogic/Models/Interfaces/IRoom.cs diff --git a/Server/GameLogic/Models/IRound.cs b/Server/GameLogic/Models/Interfaces/IRound.cs similarity index 100% rename from Server/GameLogic/Models/IRound.cs rename to Server/GameLogic/Models/Interfaces/IRound.cs diff --git a/Server/GameLogic/Models/Impl/Room.cs b/Server/GameLogic/Models/Room.cs similarity index 100% rename from Server/GameLogic/Models/Impl/Room.cs rename to Server/GameLogic/Models/Room.cs diff --git a/Server/GameLogic/Models/Impl/Round.cs b/Server/GameLogic/Models/Round.cs similarity index 100% rename from Server/GameLogic/Models/Impl/Round.cs rename to Server/GameLogic/Models/Round.cs diff --git a/Server/Startup.cs b/Server/Startup.cs index 0194853..a54c2ee 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; +using Server.Extensions; using Server.GameLogic.LogicServices; using Server.GameLogic.LogicServices.Impl; using Server.Services; @@ -23,16 +24,9 @@ public Startup(IConfiguration configuration) private IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public static void ConfigureServices(IServiceCollection services) { - - services.AddSingleton(typeof(IDeserializedObject<>), typeof(DeserializedObject<>)); - services.AddTransient(typeof(IStorage<>), typeof(Storage<>)); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - + services.AddServices(); services.AddControllers(); services.AddSwaggerGen(c => { @@ -41,7 +35,7 @@ public void ConfigureServices(IServiceCollection services) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -58,34 +52,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { - endpoints.Map("/status/{sessionId}", async context => - { - var service = context.RequestServices.GetRequiredService(); //todo: remove - - var sessionId = (string) context.Request.RouteValues["sessionId"]; - - if (sessionId == null) - { - context.Response.StatusCode = (int) HttpStatusCode.BadRequest; - } - else if (await service.IsActive(sessionId)) - { - context.Response.StatusCode = (int) HttpStatusCode.OK; - } - else - { - context.Response.StatusCode = (int) HttpStatusCode.Forbidden; - } - }); - - endpoints.Map("/status/", async context => - { - context.Response.StatusCode = (int) HttpStatusCode.OK; - await context.Response.WriteAsync("alive"); - }); - endpoints.MapControllers(); - }); } } From b29f3ba00290a9cefbbc0a37467e4f4ebb87eeb9 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 2 May 2021 14:31:23 +0300 Subject: [PATCH 02/56] Fixed namespaces --- Server/Controllers/RoomController.cs | 3 ++- Server/Controllers/RoundController.cs | 3 ++- Server/Controllers/UserController.cs | 1 + Server/Extensions/ServiceExtension.cs | 2 +- .../LogicServices/Interfaces/IRoomCoordinator.cs | 6 +++--- .../LogicServices/Interfaces/IRoundCoordinator.cs | 4 ++-- Server/GameLogic/LogicServices/RoomCoordinator.cs | 14 +++++++------- Server/GameLogic/LogicServices/RoundCoordinator.cs | 5 +++-- Server/GameLogic/Models/Interfaces/IRoom.cs | 6 +++--- Server/GameLogic/Models/Interfaces/IRound.cs | 2 +- Server/GameLogic/Models/Room.cs | 3 ++- Server/GameLogic/Models/Round.cs | 3 ++- Server/Startup.cs | 1 - 13 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Server/Controllers/RoomController.cs b/Server/Controllers/RoomController.cs index 8b95afa..b9b0fc0 100644 --- a/Server/Controllers/RoomController.cs +++ b/Server/Controllers/RoomController.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Mvc; using Server.GameLogic.LogicServices; -using Server.GameLogic.Models.Impl; using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Server.GameLogic.LogicServices.Interfaces; +using Server.GameLogic.Models; namespace Server.Controllers { diff --git a/Server/Controllers/RoundController.cs b/Server/Controllers/RoundController.cs index 66cf2b8..52e2bab 100644 --- a/Server/Controllers/RoundController.cs +++ b/Server/Controllers/RoundController.cs @@ -1,11 +1,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Server.GameLogic.LogicServices; -using Server.GameLogic.Models.Impl; using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Server.GameLogic.LogicServices.Interfaces; +using Server.GameLogic.Models; namespace Server.Controllers { diff --git a/Server/Controllers/UserController.cs b/Server/Controllers/UserController.cs index 71becc4..52a6d6b 100644 --- a/Server/Controllers/UserController.cs +++ b/Server/Controllers/UserController.cs @@ -9,6 +9,7 @@ using Server.Exceptions.LogIn; using Server.Exceptions.Register; using Server.GameLogic.LogicServices; +using Server.GameLogic.LogicServices.Interfaces; using Server.Models; using Server.Services.Interfaces; diff --git a/Server/Extensions/ServiceExtension.cs b/Server/Extensions/ServiceExtension.cs index bd3b90c..3f833ec 100644 --- a/Server/Extensions/ServiceExtension.cs +++ b/Server/Extensions/ServiceExtension.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Server.GameLogic.LogicServices; -using Server.GameLogic.LogicServices.Impl; +using Server.GameLogic.LogicServices.Interfaces; using Server.Services; using Server.Services.Interfaces; diff --git a/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs b/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs index 2517a43..3f04569 100644 --- a/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs +++ b/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs @@ -1,8 +1,8 @@ -using Server.GameLogic.Models.Impl; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Threading.Tasks; +using Server.GameLogic.Models; -namespace Server.GameLogic.LogicServices +namespace Server.GameLogic.LogicServices.Interfaces { public interface IRoomCoordinator { diff --git a/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs b/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs index bb9f9f3..c7f1f19 100644 --- a/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs +++ b/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs @@ -1,8 +1,8 @@ using System.Collections.Concurrent; using System.Threading.Tasks; -using Server.GameLogic.Models.Impl; +using Server.GameLogic.Models; -namespace Server.GameLogic.LogicServices +namespace Server.GameLogic.LogicServices.Interfaces { public interface IRoundCoordinator { diff --git a/Server/GameLogic/LogicServices/RoomCoordinator.cs b/Server/GameLogic/LogicServices/RoomCoordinator.cs index 0f8bdb5..58074f8 100644 --- a/Server/GameLogic/LogicServices/RoomCoordinator.cs +++ b/Server/GameLogic/LogicServices/RoomCoordinator.cs @@ -1,16 +1,16 @@ -using Server.Exceptions.LogIn; -using Server.GameLogic.Exceptions; -using Server.GameLogic.Models.Impl; -using Server.Models; -using Server.Services.Interfaces; -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Server.Exceptions.LogIn; +using Server.GameLogic.Exceptions; +using Server.GameLogic.LogicServices.Interfaces; using Server.GameLogic.Models; +using Server.Models; +using Server.Services.Interfaces; -namespace Server.GameLogic.LogicServices.Impl +namespace Server.GameLogic.LogicServices { public class RoomCoordinator : IRoomCoordinator { diff --git a/Server/GameLogic/LogicServices/RoundCoordinator.cs b/Server/GameLogic/LogicServices/RoundCoordinator.cs index 81ea48e..4bf8320 100644 --- a/Server/GameLogic/LogicServices/RoundCoordinator.cs +++ b/Server/GameLogic/LogicServices/RoundCoordinator.cs @@ -2,12 +2,13 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; +using Server.GameLogic.LogicServices.Interfaces; using Server.GameLogic.Models; -using Server.GameLogic.Models.Impl; +using Server.GameLogic.Models.Interfaces; using Server.Models; using Server.Services.Interfaces; -namespace Server.GameLogic.LogicServices.Impl +namespace Server.GameLogic.LogicServices { public class RoundCoordinator : IRoundCoordinator { diff --git a/Server/GameLogic/Models/Interfaces/IRoom.cs b/Server/GameLogic/Models/Interfaces/IRoom.cs index de5ec72..7c54c28 100644 --- a/Server/GameLogic/Models/Interfaces/IRoom.cs +++ b/Server/GameLogic/Models/Interfaces/IRoom.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Concurrent; +using Newtonsoft.Json; -namespace Server.GameLogic.Models +namespace Server.GameLogic.Models.Interfaces { public interface IRoom { diff --git a/Server/GameLogic/Models/Interfaces/IRound.cs b/Server/GameLogic/Models/Interfaces/IRound.cs index 96b9509..abee617 100644 --- a/Server/GameLogic/Models/Interfaces/IRound.cs +++ b/Server/GameLogic/Models/Interfaces/IRound.cs @@ -2,7 +2,7 @@ using System.Collections.Concurrent; using System.Text.Json.Serialization; -namespace Server.GameLogic.Models +namespace Server.GameLogic.Models.Interfaces { public interface IRound { diff --git a/Server/GameLogic/Models/Room.cs b/Server/GameLogic/Models/Room.cs index f97de69..ac8425e 100644 --- a/Server/GameLogic/Models/Room.cs +++ b/Server/GameLogic/Models/Room.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Concurrent; +using Server.GameLogic.Models.Interfaces; -namespace Server.GameLogic.Models.Impl +namespace Server.GameLogic.Models { public class Room : IRoom { diff --git a/Server/GameLogic/Models/Round.cs b/Server/GameLogic/Models/Round.cs index 40051be..bd4ab85 100644 --- a/Server/GameLogic/Models/Round.cs +++ b/Server/GameLogic/Models/Round.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Concurrent; +using Server.GameLogic.Models.Interfaces; -namespace Server.GameLogic.Models.Impl +namespace Server.GameLogic.Models { public class Round : IRound { diff --git a/Server/Startup.cs b/Server/Startup.cs index a54c2ee..bfe807e 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -8,7 +8,6 @@ using Microsoft.OpenApi.Models; using Server.Extensions; using Server.GameLogic.LogicServices; -using Server.GameLogic.LogicServices.Impl; using Server.Services; using Server.Services.Interfaces; From 39492258521ba484ea3e996e2a4d1603d9ca840e Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 2 May 2021 14:42:10 +0300 Subject: [PATCH 03/56] refactoring. Added client --- Client/ClientAppEmulator.cs | 8 +- Client/Models/Account.cs | 9 -- Client/Models/Interfaces/IAccount.cs | 2 - Client/Program.cs | 11 +- .../RequestProcessor/IRequestHandler.cs | 1 + .../RequestProcessor/IRequestPerformer.cs | 1 + .../RequestProcessor/Impl/RequestHandler.cs | 11 +- .../RequestProcessor/Impl/RequestPerformer.cs | 11 +- .../RequestModels/IResponse.cs | 2 +- .../LogicServices/RoomCoordinator.cs | 2 +- .../LogicServices/RoundCoordinator.cs | 128 ++++++++---------- Server/Services/DeserializedObject.cs | 2 +- 12 files changed, 93 insertions(+), 95 deletions(-) diff --git a/Client/ClientAppEmulator.cs b/Client/ClientAppEmulator.cs index 1efd022..1a8ef15 100644 --- a/Client/ClientAppEmulator.cs +++ b/Client/ClientAppEmulator.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -280,7 +281,7 @@ private async Task JoinPrivateRoom() logger.Info("Error occured. Probably there is either no room or it is already full"); } } - /**/ + /*#1# private async Task StartRoomMenu() { @@ -312,7 +313,7 @@ private async Task StartRoomMenu() Console.ReadKey(); } } - /**/ + /*#1# private async Task MakeYourMove() { var move = 0; @@ -711,5 +712,6 @@ private static void PrintStatistics(IEnumerable statisticsEnumera } } } +*/ \ No newline at end of file diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs index 5f9de0b..8dc92fd 100644 --- a/Client/Models/Account.cs +++ b/Client/Models/Account.cs @@ -7,9 +7,6 @@ namespace Client.Models internal class Account : IAccount { - [JsonProperty("SessionId")] - public string SessionId { get; set; } - [JsonProperty("Login")] public string Login { get; set; } @@ -18,11 +15,5 @@ internal class Account : IAccount [JsonProperty("LastRequest")] public DateTime LastRequest { get; set; } - //soon wil be deleted - public override string ToString() - { - return $"Login:\t{Login}\n" + - $"Password:\t{Password}"; - } } } diff --git a/Client/Models/Interfaces/IAccount.cs b/Client/Models/Interfaces/IAccount.cs index 6babd23..94614e9 100644 --- a/Client/Models/Interfaces/IAccount.cs +++ b/Client/Models/Interfaces/IAccount.cs @@ -4,10 +4,8 @@ namespace Client.Models.Interfaces { internal interface IAccount { - public string SessionId { get; set; } public string Login { get; set; } public string Password { get; set; } public DateTime LastRequest { get; set; } - //soon wil be deleted } } diff --git a/Client/Program.cs b/Client/Program.cs index 99c05fc..4ddb587 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -1,6 +1,8 @@ using Client.Services.RequestProcessor.Impl; using System; +using System.Net.Http; using System.Threading.Tasks; +using Client.Menus; namespace Client { @@ -10,8 +12,13 @@ private static async Task Main() { try { - var emulator = new ClientAppEmulator(new RequestPerformer()); - return await emulator.StartAsync(); + var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/")}; + var requestHandler = new RequestHandler(client); + var performer = new RequestPerformer(requestHandler); + //var emulator = new ClientAppEmulator(new RequestPerformer()); + + var startMenu = new StartMenu(performer); + return await startMenu.StartAsync(); } catch (Exception) //todo : do this need a message? { diff --git a/Client/Services/RequestProcessor/IRequestHandler.cs b/Client/Services/RequestProcessor/IRequestHandler.cs index 61e8955..fed8de0 100644 --- a/Client/Services/RequestProcessor/IRequestHandler.cs +++ b/Client/Services/RequestProcessor/IRequestHandler.cs @@ -2,6 +2,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using Client.Services.RequestProcessor.RequestModels; namespace Client.Services.RequestProcessor { diff --git a/Client/Services/RequestProcessor/IRequestPerformer.cs b/Client/Services/RequestProcessor/IRequestPerformer.cs index a028f21..68aa3c8 100644 --- a/Client/Services/RequestProcessor/IRequestPerformer.cs +++ b/Client/Services/RequestProcessor/IRequestPerformer.cs @@ -2,6 +2,7 @@ using Client.Services.RequestProcessor.RequestModels.Impl; using System; using System.Threading.Tasks; +using Client.Services.RequestProcessor.RequestModels; namespace Client.Services.RequestProcessor { diff --git a/Client/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Services/RequestProcessor/Impl/RequestHandler.cs index d43634e..d9e18dc 100644 --- a/Client/Services/RequestProcessor/Impl/RequestHandler.cs +++ b/Client/Services/RequestProcessor/Impl/RequestHandler.cs @@ -7,21 +7,26 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Client.Services.RequestProcessor.RequestModels; namespace Client.Services.RequestProcessor.Impl { public class RequestHandler : IRequestHandler { - private HttpClient _client; + private readonly HttpClient _client; + + public RequestHandler(HttpClient httpClient) + { + _client = httpClient; + } public async Task HandleRequestAsync(IRequestOptions requestOptions) { var handler = new HttpClientHandler(); handler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, errors) => true; - _client = new HttpClient(handler); if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - using var msg = new HttpRequestMessage(MapMethod(requestOptions.Method), new Uri(requestOptions.Address)); + using var msg = new HttpRequestMessage(MapMethod(requestOptions.Method), new Uri(_client.BaseAddress+requestOptions.Address)); try { if (MapMethod(requestOptions.Method) == HttpMethod.Delete) diff --git a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs index e5b73d5..972309a 100644 --- a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs +++ b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs @@ -5,16 +5,19 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Client.Services.RequestProcessor.RequestModels; namespace Client.Services.RequestProcessor.Impl { public class RequestPerformer : IRequestPerformer { - public RequestPerformer() + private readonly IRequestHandler _requestHandler; + public RequestPerformer( + IRequestHandler requestHandler) { - RequestHandler = new RequestHandler(); + _requestHandler = requestHandler; } - public readonly IRequestHandler RequestHandler; + //public readonly IRequestHandler RequestHandler; public async Task PerformRequestAsync(IRequestOptions requestOptions) { IResponse response = null; @@ -22,7 +25,7 @@ public async Task PerformRequestAsync(IRequestOptions requestOptions) if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); try { - response = await RequestHandler.HandleRequestAsync(requestOptions); + response = await _requestHandler.HandleRequestAsync(requestOptions); } catch (TimeoutException) //todo: Probably redo { diff --git a/Client/Services/RequestProcessor/RequestModels/IResponse.cs b/Client/Services/RequestProcessor/RequestModels/IResponse.cs index 2387385..2d84061 100644 --- a/Client/Services/RequestProcessor/RequestModels/IResponse.cs +++ b/Client/Services/RequestProcessor/RequestModels/IResponse.cs @@ -1,4 +1,4 @@ -namespace Client.Services.RequestModels +namespace Client.Services.RequestProcessor.RequestModels { public interface IResponse { diff --git a/Server/GameLogic/LogicServices/RoomCoordinator.cs b/Server/GameLogic/LogicServices/RoomCoordinator.cs index 58074f8..6af6d41 100644 --- a/Server/GameLogic/LogicServices/RoomCoordinator.cs +++ b/Server/GameLogic/LogicServices/RoomCoordinator.cs @@ -261,7 +261,7 @@ public async Task UpdateRoom(string roomId) return await UpdateRoom(room); } - catch (Exception exception) + catch (Exception) { return null; } diff --git a/Server/GameLogic/LogicServices/RoundCoordinator.cs b/Server/GameLogic/LogicServices/RoundCoordinator.cs index 4bf8320..f9ee69b 100644 --- a/Server/GameLogic/LogicServices/RoundCoordinator.cs +++ b/Server/GameLogic/LogicServices/RoundCoordinator.cs @@ -136,7 +136,7 @@ public async Task MakeMove(string roomId, string sessionId, int move) thisRound.LoserId = _accountManager.AccountsActive.FirstOrDefault(x=> x.Value.Id==loserId).Value.Login; thisRound.TimeFinished = DateTime.Now; } - _storageRounds.Add(thisRound); + await _storageRounds.AddAsync(thisRound); await FillStatistics(thisRound); } @@ -216,7 +216,7 @@ private async Task FillStatistics(IRound thisRound) } } - var winRate = 0d; + double winRate; if (loss == 0) winRate = 0d; else @@ -238,90 +238,80 @@ private static bool InRange(DateTime dateToCheck, DateTime startDate, DateTime e //***************************** - private async Task UpdateRound(Round updated) - { - var task = Task.Factory.StartNew(async () => - { - var roomId = - ActiveRounds.Where(x => x.Value - .Equals(updated)).ToArray(); + private async Task UpdateRound(Round updated) + { + var roomId = + ActiveRounds.Where(x => x.Value + .Equals(updated)).ToArray(); + if (updated.IsFinished) + { + //if (!updated.PlayerMoves.All(x => x.Key != "Bot") || updated.IsDraw) return updated; - if (updated.IsFinished) - { - //if (!updated.PlayerMoves.All(x => x.Key != "Bot") || updated.IsDraw) return updated; - + //ActiveRounds.TryRemove(roomId[0].Key, out _); - //ActiveRounds.TryRemove(roomId[0].Key, out _); + await Task.FromResult(updated); + return; + } + ActiveRounds.TryGetValue(roomId[0].Key, out var oldRoom); + ActiveRounds.TryUpdate(roomId[0].Key, updated, oldRoom); - return updated; - } - ActiveRounds.TryGetValue(roomId[0].Key, out var oldRoom); - ActiveRounds.TryUpdate(roomId[0].Key, updated, oldRoom); + await Task.FromResult(updated); - return updated; - }); - - return await await task; } //******************************** public async Task UpdateRound(string roomId) - { - var task = Task.Factory.StartNew(async () => + { + ActiveRounds.TryGetValue(roomId, out var updated); + + if (updated == null) + return null; //todo: add exception; + + await CheckTime(updated); + if (updated.IsFinished) { - ActiveRounds.TryGetValue(roomId, out var updated); - - if (updated == null) - return null; //todo: add exception; - - //*************************************************************** - var elapsedTime = DateTime.Now.Subtract(updated.TimeFinished); - if (elapsedTime.Seconds>= 20 && - updated.PlayerMoves.Any(x => x.Value.Equals(RequiredGameMove.Default))) - { - var dictionary = updated.PlayerMoves; - var first = dictionary.Keys.First(); - var last = dictionary.Keys.First(); + //if(updated.PlayerMoves.Keys.Any(x=> x!="Bot")) + //await _storageRounds.AddAsync(updated); - if (dictionary[first] == dictionary[last]) - updated.IsDraw = false; - else if (dictionary[first] == RequiredGameMove.Default) - { - updated.LoserId = first; - updated.WinnerId = last; - } - else - { - updated.LoserId = last; - updated.WinnerId = first; - } - updated.TimeFinished = DateTime.Now; - updated.IsFinished = true; - - await UpdateRound(updated); - return updated; - } - //***************************************************************** - - if (updated.IsFinished) - { - //if(updated.PlayerMoves.Keys.Any(x=> x!="Bot")) - //await _storageRounds.AddAsync(updated); + //ActiveRounds.TryRemove(roomId, out _); - //ActiveRounds.TryRemove(roomId, out _); + return updated; + } - return updated; - } + ActiveRounds.TryGetValue(roomId, out var oldRoom); //Do something with this + ActiveRounds.TryUpdate(roomId, updated, oldRoom); - ActiveRounds.TryGetValue(roomId, out var oldRoom); //Do something with this - ActiveRounds.TryUpdate(roomId, updated, oldRoom); + return updated; + } - return updated; - }); + private async Task CheckTime(Round updated) + { + var elapsedTime = DateTime.Now.Subtract(updated.TimeFinished); + if (elapsedTime.Seconds < 20 || + !updated.PlayerMoves.Any(x => x.Value.Equals(RequiredGameMove.Default))) + return; + var dictionary = updated.PlayerMoves; + var first = dictionary.Keys.First(); + var last = dictionary.Keys.First(); - return await await task; //Task>?????????????????? + if (dictionary[first] == dictionary[last]) + updated.IsDraw = false; + else if (dictionary[first] == RequiredGameMove.Default) + { + updated.LoserId = first; + updated.WinnerId = last; + } + else + { + updated.LoserId = last; + updated.WinnerId = first; + } + updated.TimeFinished = DateTime.Now; + updated.IsFinished = true; + + await UpdateRound(updated); } public async Task GetCurrentActiveRoundForSpecialRoom(string roundId) { diff --git a/Server/Services/DeserializedObject.cs b/Server/Services/DeserializedObject.cs index f934d17..984b2b6 100644 --- a/Server/Services/DeserializedObject.cs +++ b/Server/Services/DeserializedObject.cs @@ -79,7 +79,7 @@ private async Task> Deserialize() JsonConvert.DeserializeObject>(decoded)); return list; } - catch (FileNotFoundException exception) + catch (FileNotFoundException) { File.Create(_fileName); return new ConcurrentDictionary(); From 1772e0a5b8db86f3d6d66a2dd193a4e149ea56fb Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 2 May 2021 16:01:34 +0300 Subject: [PATCH 04/56] Some refactoring --- Client/Client.csproj | 2 +- Client/ClientAppEmulator.cs | 56 --------- Client/Menus/AccountMenu.cs | 109 ++++++++++++++++ Client/Menus/IAccountMenu.cs | 11 ++ Client/Menus/IMainMenu.cs | 9 ++ Client/Menus/MainMenu.cs | 74 +++++++++++ Client/Menus/StartMenu.cs | 119 ++++++++++++++++++ Client/Models/Account.cs | 3 +- Client/Models/Interfaces/IAccount.cs | 4 +- Client/Services/ColorTextWriterService.cs | 23 ---- Client/Services/StringPlaceholder.cs | 57 ++++----- Client/Services/TextWrite.cs | 14 +++ Server/Contracts/AccountDto.cs | 2 - Server/Controllers/UserController.cs | 8 +- Server/Extensions/LoggingMiddleware.cs | 119 ++++++++++++++++++ Server/Server.csproj | 7 +- Server/Services/AccountManager.cs | 74 +++++------ Server/Services/Interfaces/IAccountManager.cs | 2 +- Server/Startup.cs | 1 + 19 files changed, 527 insertions(+), 167 deletions(-) create mode 100644 Client/Menus/AccountMenu.cs create mode 100644 Client/Menus/IAccountMenu.cs create mode 100644 Client/Menus/IMainMenu.cs create mode 100644 Client/Menus/MainMenu.cs create mode 100644 Client/Menus/StartMenu.cs delete mode 100644 Client/Services/ColorTextWriterService.cs create mode 100644 Client/Services/TextWrite.cs create mode 100644 Server/Extensions/LoggingMiddleware.cs diff --git a/Client/Client.csproj b/Client/Client.csproj index 00d2bb4..9140622 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -6,7 +6,7 @@ - + diff --git a/Client/ClientAppEmulator.cs b/Client/ClientAppEmulator.cs index 1a8ef15..04d0b57 100644 --- a/Client/ClientAppEmulator.cs +++ b/Client/ClientAppEmulator.cs @@ -136,63 +136,7 @@ private async Task StartMenu(CancellationToken token) } } - private async Task PlayerMenu() - { - while (true) - { - ColorTextWriterService.PrintLineMessageWithSpecialColor($"***\nHello, {_playerAccount.Login}\n" + - "Please choose option", ConsoleColor.Cyan); - ColorTextWriterService.PrintLineMessageWithSpecialColor("1.\tPlay with bot\n" + - "2\tCreate room\n" + - "3\tJoin Private room\n" + - "4\tJoin Public room\n" + - "5\tShow Statistics\n" + - "6\tLog out", ConsoleColor.Yellow); - - ColorTextWriterService.PrintLineMessageWithSpecialColor("\nPlease select an item from the list", ConsoleColor.Green); - Console.Write("Select -> "); - var passed = int.TryParse(Console.ReadLine(), out int playersMenuInput); - if (!passed) - { - logger.Trace($"Not passed argument to player Menu"); - ColorTextWriterService.PrintLineMessageWithSpecialColor("Unsupported input", ConsoleColor.Red); - continue; - } - switch (playersMenuInput) - { - case 1: - Console.Clear(); - await JoinRoomWithBot(); - break; - case 2: - Console.Clear(); - await CreationRoom(); - break; - case 3: - Console.Clear(); - await JoinPrivateRoom(); - break; - case 4: - Console.Clear(); - await JoinPublicRoom(); - break; - case 5: - Console.Clear(); - var statistics = await PersonalStatistics(_sessionId); - Console.WriteLine(statistics+"\n\nPress any key to go back."); - Console.ReadKey(); - break; - case 6: - Console.Clear(); - await Logout(); - return; - default: - ColorTextWriterService.PrintLineMessageWithSpecialColor("Unsupported input", ConsoleColor.Red); - continue; - } - } - } private async Task JoinRoomWithBot() { logger.Trace("JoinRoomWithBot method"); diff --git a/Client/Menus/AccountMenu.cs b/Client/Menus/AccountMenu.cs new file mode 100644 index 0000000..88eae68 --- /dev/null +++ b/Client/Menus/AccountMenu.cs @@ -0,0 +1,109 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Client.Models; +using Client.Services; +using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels.Impl; +using Newtonsoft.Json; + +namespace Client.Menus +{ + public class AccountMenu : IAccountMenu + { + private readonly IRequestPerformer _performer; + + public AccountMenu(IRequestPerformer performer) + { + _performer = performer; + } + + public async Task Register() + { + TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + + "Please enter the required details\n" + + "to register an account on the platform", ConsoleColor.Magenta); + var registrationAccount = new Account + { + Login = new StringPlaceholder().BuildString("Login"), + Password = + new StringPlaceholder(StringDestination.Password).BuildString("Password"), + LastRequest = DateTime.Now + }; + + var options = new RequestOptions + { + ContentType = "application/json", + Body = JsonConvert.SerializeObject(registrationAccount), + Address = "user/register", + IsValid = true, + Method = Services.RequestModels.RequestMethod.Post, + Name = "Registration" + }; + var reachedResponse = await _performer.PerformRequestAsync(options); + if (reachedResponse.Code == (int) HttpStatusCode.Created) + { + + TextWrite.Print(reachedResponse.Content, ConsoleColor.Green); + return true; + } + + TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); + return false; + } + + public async Task<(string sessionId, Account inputAccount)> LogIn() + { + var inputAccount = new Account + { + Login = new StringPlaceholder().BuildString("Login"), + Password = + new StringPlaceholder(StringDestination.Password).BuildString("Password", true), + LastRequest = DateTime.Now + }; + var options = new RequestOptions + { + ContentType = "application/json", + Body = JsonConvert.SerializeObject(inputAccount), + Address = "user/login", + IsValid = true, + Method = Services.RequestModels.RequestMethod.Post, + Name = "Login" + }; + var reachedResponse = await _performer.PerformRequestAsync(options); + if (reachedResponse.Code == (int) HttpStatusCode.OK) + { + TextWrite.Print(reachedResponse.Content, ConsoleColor.Green); + + //returns sessionId + var sessionId = reachedResponse.Content.Replace("\"","").Trim(); + TextWrite.Print($"Successfully signed in! session id : {sessionId}", ConsoleColor.DarkGreen); + return (sessionId, inputAccount); + } + + TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); + + return (null, null); + } + + public async Task Logout(string sessionId) + { + var options = new RequestOptions + { + Address = $"user/logout/{sessionId}", + IsValid = true, + Method = Services.RequestModels.RequestMethod.Get + }; + var reachedResponse = await _performer.PerformRequestAsync(options); + if (reachedResponse.Code == 200) + { + TextWrite.Print("Successfully signed out", ConsoleColor.Green); + return true; + } + + TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); + + return false; + } + } +} \ No newline at end of file diff --git a/Client/Menus/IAccountMenu.cs b/Client/Menus/IAccountMenu.cs new file mode 100644 index 0000000..acdaa17 --- /dev/null +++ b/Client/Menus/IAccountMenu.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Client.Menus +{ + public interface IAccountMenu + { + Task Register(); + Task<(string sessionId, Models.Account inputAccount)> LogIn(); + Task Logout(string sessionId); + } +} \ No newline at end of file diff --git a/Client/Menus/IMainMenu.cs b/Client/Menus/IMainMenu.cs new file mode 100644 index 0000000..fd7baaa --- /dev/null +++ b/Client/Menus/IMainMenu.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Client.Menus +{ + public interface IMainMenu + { + Task PlayerMenu(); + } +} \ No newline at end of file diff --git a/Client/Menus/MainMenu.cs b/Client/Menus/MainMenu.cs new file mode 100644 index 0000000..1879bfa --- /dev/null +++ b/Client/Menus/MainMenu.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading.Tasks; +using Client.Models.Interfaces; +using Client.Services; + +namespace Client.Menus +{ + public class MainMenu : IMainMenu + { + private readonly IAccount _playerAccount; + + public MainMenu(IAccount account) + { + _playerAccount = account; + } + + public async Task PlayerMenu() + { + while (true) + { + TextWrite.Print($"***\nHello, {_playerAccount.Login}\n" + + "Please choose option", ConsoleColor.Cyan); + TextWrite.Print("1.\tPlay with bot\n" + + "2\tCreate room\n" + + "3\tJoin Private room\n" + + "4\tJoin Public room\n" + + "5\tShow Statistics\n" + + "6\tLog out", ConsoleColor.Yellow); + + TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); + + Console.Write("Select -> "); + var passed = int.TryParse(Console.ReadLine(), out var playersMenuInput); + if (!passed) + { + TextWrite.Print("Unsupported input", ConsoleColor.Red); + continue; + } + /*switch (playersMenuInput) + { + case 1: + Console.Clear(); + await JoinRoomWithBot(); + break; + case 2: + Console.Clear(); + await CreationRoom(); + break; + case 3: + Console.Clear(); + await JoinPrivateRoom(); + break; + case 4: + Console.Clear(); + await JoinPublicRoom(); + break; + case 5: + Console.Clear(); + var statistics = await PersonalStatistics(_sessionId); + Console.WriteLine(statistics+"\n\nPress any key to go back."); + Console.ReadKey(); + break; + case 6: + Console.Clear(); + await Logout(); + return; + default: + TextWrite.PrintLineMessageWithSpecialColor("Unsupported input", ConsoleColor.Red); + continue; + }*/ + } + } + } +} \ No newline at end of file diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs new file mode 100644 index 0000000..b156a49 --- /dev/null +++ b/Client/Menus/StartMenu.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Client.Models; +using Client.Services; +using Client.Services.RequestProcessor; + +namespace Client.Menus +{ + public class StartMenu + { + private readonly IAccountMenu _accountMenu; + + private string SessionId { get; set; } + + public StartMenu(IRequestPerformer performer) + { + _accountMenu = new AccountMenu(performer); + } + + public async Task StartAsync() + { + var tokenSource = new CancellationTokenSource(); + var token = tokenSource.Token; + Greeting(); + TextWrite.Print("\n\nPress any key to show start up menu list!", ConsoleColor.Green); + Console.ReadKey(); + Console.Clear(); + //todo: trying to connect to the server + await Menu(token); + return 1; + } + private async Task Menu(CancellationToken token) + { + while (true) + { + TextWrite.Print("" + + "Please auth to proceed:\n" + + "1.\tSign up\n" + + "2.\tLog in\n" + + "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics + "4.\tExit", ConsoleColor.DarkYellow); + + TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); + + Console.Write("Select -> "); + if (token.IsCancellationRequested) + { + return; + } + var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); + if (!passed) + { + TextWrite.Print("Unsupported input", ConsoleColor.Red); + continue; + } + switch (startMenuInput) + { + case 1: + await _accountMenu.Register(); + TextWrite.Print( + "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); + Console.ReadKey(); + Console.Clear(); + break; + case 2: + Account inputAccount; + (SessionId, inputAccount) = await _accountMenu.LogIn(); + if (string.IsNullOrEmpty(SessionId)) + { + Console.WriteLine("Failed to log in"); + Console.Clear(); + } + else + { + Console.ReadKey(); + Console.Clear(); + await new MainMenu(inputAccount).PlayerMenu(); + } + break; + case 3: + /*var statistics = await OverallStatistics(); + if(statistics == null) + Console.WriteLine("No statistics so far"); + else + { + PrintStatistics(statistics); + }*/ + break; + case 4: + if (await _accountMenu.Logout(SessionId)) + { + Console.WriteLine("DEBUG: Logged out"); + return; + } + else + { + throw new NotImplementedException(); + } + default: + TextWrite.Print("Unsupported input", ConsoleColor.Red); + continue; + } + + } + + } + + private static void Greeting() + { + TextWrite.Print( + "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + + "You are given the opportunity to compete with other users in this wonderful game,\n" + + "or if you don’t have anyone to play, don’t worry,\n" + + "you can find a random player or just try your skill with a bot.", ConsoleColor.White); + TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); + } + } +} \ No newline at end of file diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs index 8dc92fd..44a8703 100644 --- a/Client/Models/Account.cs +++ b/Client/Models/Account.cs @@ -4,9 +4,8 @@ namespace Client.Models { - internal class Account : IAccount + public class Account : IAccount { - [JsonProperty("Login")] public string Login { get; set; } diff --git a/Client/Models/Interfaces/IAccount.cs b/Client/Models/Interfaces/IAccount.cs index 94614e9..45711af 100644 --- a/Client/Models/Interfaces/IAccount.cs +++ b/Client/Models/Interfaces/IAccount.cs @@ -2,10 +2,10 @@ namespace Client.Models.Interfaces { - internal interface IAccount + public interface IAccount { public string Login { get; set; } public string Password { get; set; } public DateTime LastRequest { get; set; } } -} +} \ No newline at end of file diff --git a/Client/Services/ColorTextWriterService.cs b/Client/Services/ColorTextWriterService.cs deleted file mode 100644 index 14dc144..0000000 --- a/Client/Services/ColorTextWriterService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Client.Services -{ - public static class ColorTextWriterService - { - public static void PrintLineMessageWithSpecialColor(string msg, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.WriteLine(msg); - Console.ResetColor(); - } - - public static void PrintMessageWithSpecialColor(string msg, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.Write(msg); - Console.ResetColor(); - } - } -} diff --git a/Client/Services/StringPlaceholder.cs b/Client/Services/StringPlaceholder.cs index b7fc5b8..76ea0d0 100644 --- a/Client/Services/StringPlaceholder.cs +++ b/Client/Services/StringPlaceholder.cs @@ -1,80 +1,77 @@ using System; -using System.Collections.Generic; -using System.Text; using Client.Models; using Client.Validations; namespace Client.Services { - class StringPlaceholder + internal class StringPlaceholder { - private StringDestination destination; + private readonly StringDestination _destination; public StringPlaceholder() { - destination = StringDestination.Login; + _destination = StringDestination.Login; } public StringPlaceholder(StringDestination destination) { - this.destination = destination; + this._destination = destination; } - public string BuildNewSpecialDestinationString(string msg, bool isNeedConfirmation = false) + public string BuildString(string msg, bool isNeedConfirmation = false) { string output; while (true) { - bool passwordNotConfirmed = true; - if(destination == StringDestination.PassportType || destination == StringDestination.Email) - ColorTextWriterService.PrintLineMessageWithSpecialColor($"What is your {msg}?", ConsoleColor.Yellow); + var passwordNotConfirmed = true; + if(_destination == StringDestination.PassportType || _destination == StringDestination.Email) + TextWrite.Print($"What is your {msg}?", ConsoleColor.Yellow); else - ColorTextWriterService.PrintLineMessageWithSpecialColor($"Try to come up with {msg}?", ConsoleColor.Yellow); + TextWrite.Print($"Try to come up with {msg}?", ConsoleColor.Yellow); Console.Write($"{msg}--> "); output = Console.ReadLine() ?.Trim() .Replace(" ", ""); if (String.IsNullOrEmpty(output)) { - ColorTextWriterService.PrintLineMessageWithSpecialColor("Wrong data!", ConsoleColor.Red); + TextWrite.Print("Wrong data!", ConsoleColor.Red); continue; } - if(destination == StringDestination.Password && output.Length < 6) + switch (_destination) { - ColorTextWriterService.PrintLineMessageWithSpecialColor("Wrong password length!", ConsoleColor.Red); - continue; + case StringDestination.Password when output.Length < 6: + TextWrite.Print("Wrong password length!", ConsoleColor.Red); + continue; + case StringDestination.Email when !StringValidator.IsEmailValid(output): + TextWrite.Print("This email is not valid!", ConsoleColor.Red); + continue; } - if (destination == StringDestination.Email && !StringValidator.IsEmailValid(output)) - { - ColorTextWriterService.PrintLineMessageWithSpecialColor("This email is not valid!", ConsoleColor.Red); - continue; - } - if (destination == StringDestination.Password) + + if (_destination == StringDestination.Password) { if (isNeedConfirmation == true) break; - ColorTextWriterService.PrintLineMessageWithSpecialColor("You need to confirm password!", ConsoleColor.Yellow); - string confirmationPassword; + TextWrite.Print("You need to confirm password!", ConsoleColor.Yellow); do { Console.Write("Confirmation--> "); - confirmationPassword = Console.ReadLine() + var confirmationPassword = Console.ReadLine() ?.Trim() .Replace(" ", ""); - if (String.IsNullOrEmpty(output)) + if (string.IsNullOrEmpty(output)) { - ColorTextWriterService.PrintLineMessageWithSpecialColor("Wrong data!", ConsoleColor.Red); + TextWrite.Print("Wrong data!", ConsoleColor.Red); continue; } if (output == confirmationPassword) { - ColorTextWriterService.PrintLineMessageWithSpecialColor("Password confirmed", ConsoleColor.Green); + TextWrite.Print("Password confirmed", ConsoleColor.Green); passwordNotConfirmed = false; } else - ColorTextWriterService.PrintLineMessageWithSpecialColor("Passwords dont match!",ConsoleColor.Red); + TextWrite.Print("Passwords dont match!",ConsoleColor.Red); } while (passwordNotConfirmed); } - if (destination == StringDestination.PassportType && StringValidator.IsStringContainsDigits(output)) + if (_destination == StringDestination.PassportType && StringValidator.IsStringContainsDigits(output)) { - ColorTextWriterService.PrintLineMessageWithSpecialColor("You cannot enter nameType with digits!", ConsoleColor.Red); + TextWrite.Print("You cannot enter nameType with digits!", ConsoleColor.Red); continue; } break; diff --git a/Client/Services/TextWrite.cs b/Client/Services/TextWrite.cs new file mode 100644 index 0000000..0197dbc --- /dev/null +++ b/Client/Services/TextWrite.cs @@ -0,0 +1,14 @@ +using System; + +namespace Client.Services +{ + public static class TextWrite + { + public static void Print(string msg, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.WriteLine(msg); + Console.ResetColor(); + } + } +} diff --git a/Server/Contracts/AccountDto.cs b/Server/Contracts/AccountDto.cs index bec930a..d68bf22 100644 --- a/Server/Contracts/AccountDto.cs +++ b/Server/Contracts/AccountDto.cs @@ -5,8 +5,6 @@ namespace Server.Contracts { public class AccountDto { - public string SessionId { get; set; } - [Required(ErrorMessage = "Login is required!")] public string Login { get; init; } [Required(ErrorMessage = "Password is required!!")] diff --git a/Server/Controllers/UserController.cs b/Server/Controllers/UserController.cs index 52a6d6b..186700f 100644 --- a/Server/Controllers/UserController.cs +++ b/Server/Controllers/UserController.cs @@ -8,7 +8,6 @@ using Server.Contracts; using Server.Exceptions.LogIn; using Server.Exceptions.Register; -using Server.GameLogic.LogicServices; using Server.GameLogic.LogicServices.Interfaces; using Server.Models; using Server.Services.Interfaces; @@ -17,7 +16,6 @@ namespace Server.Controllers { [ApiController] [Route ("/user")] - [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] public class UserController : ControllerBase @@ -51,12 +49,12 @@ IRoomCoordinator roomCoordinator [Route("login")] [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task> Login(AccountDto accountDto) + public async Task> Login(AccountDto accountDto) { try { await _accountManager.LogInAsync(accountDto); - return Ok($"Signed In as {accountDto.Login}"); + return Ok(accountDto); } catch (ValidationException exception) @@ -78,7 +76,7 @@ public async Task> Login(AccountDto accountDto) /// Data Transfer Object of account /// HttpStatusCode and response string [HttpPost] - [Route("create")] + [Route("register")] [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] public async Task> CreateAccount(AccountDto accountDto) diff --git a/Server/Extensions/LoggingMiddleware.cs b/Server/Extensions/LoggingMiddleware.cs new file mode 100644 index 0000000..17a2d8b --- /dev/null +++ b/Server/Extensions/LoggingMiddleware.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Server.Extensions +{ + /// + /// Middleware to log raw data + /// + internal class LoggingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Constructor + /// + /// + /// + public LoggingMiddleware( + RequestDelegate next, + ILoggerFactory loggerFactory) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + } + + /// + /// Invoke method of logging. + /// + /// + /// + public async Task Invoke(HttpContext context) + { + var requestInformation = $"Request information:\n" + + $"Schema:{context.Request.Scheme}\n" + + $"Host:{context.Request.Host}\n" + + $"Path:{context.Request.Path}\n" + + $"QueryString:{context.Request.QueryString}\n" + + $"Request Body:{await ObtainRequestBody(context.Request)}\n"; + _logger.LogWarning(requestInformation); + + var originalResponseBody = context.Response.Body; + await using var responseBody = new MemoryStream(); + + context.Response.Body = responseBody; + await _next(context); + + var status = GetStatusCode(context); + var level = GetLogLevel(status); + + _logger.Log(level, "Response body: LogLevel: {0}; Code: {1}\n Body: {2}", + GetLogLevel(status),status,await ObtainResponseBody(context)); + + await responseBody.CopyToAsync(originalResponseBody); + + } + + private static async Task ObtainRequestBody(HttpRequest request) + { + if (request.Body == null) return string.Empty; + request.EnableBuffering(); + var encoding = GetEncodingFromContentType(request.ContentType); + string bodyStr; + + using (var reader = new StreamReader(request.Body, encoding, true, 1024, true)) + { + bodyStr = await reader.ReadToEndAsync().ConfigureAwait(false); + } + request.Body.Seek(0, SeekOrigin.Begin); + return bodyStr; + } + private static async Task ObtainResponseBody(HttpContext context) + { + var response = context.Response; + response.Body.Seek(0, SeekOrigin.Begin); + var encoding = GetEncodingFromContentType(response.ContentType); + using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: + false, bufferSize: 4096, leaveOpen: true); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + response.Body.Seek(0, SeekOrigin.Begin); + return text; + } + private static Encoding GetEncodingFromContentType(string contentTypeStr) + { + if (string.IsNullOrEmpty(contentTypeStr)) + { + return Encoding.UTF8; + } + ContentType contentType; + try + { + contentType = new ContentType(contentTypeStr); + } + catch (FormatException) + { + return Encoding.UTF8; + } + if (string.IsNullOrEmpty(contentType.CharSet)) + { + return Encoding.UTF8; + } + return Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + } + private static LogLevel GetLogLevel(int? statusCode) + { + return statusCode > 399 ? LogLevel.Error : LogLevel.Information; + } + private static int? GetStatusCode(HttpContext context) + { + return context.Response?.StatusCode; + } + + } +} \ No newline at end of file diff --git a/Server/Server.csproj b/Server/Server.csproj index cf71a1d..5c96d0a 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -5,12 +5,13 @@ - - + + - + + diff --git a/Server/Services/AccountManager.cs b/Server/Services/AccountManager.cs index 904e767..c1c2a15 100644 --- a/Server/Services/AccountManager.cs +++ b/Server/Services/AccountManager.cs @@ -41,57 +41,49 @@ public AccountManager( /// When too many false retries /// When invalid data /// When used is already signed in - public async Task LogInAsync(AccountDto accountDto) - { - var tasks = Task.Factory.StartNew(() => - { - var invalidTryAccount = _invalidTries.FirstOrDefault(x => x.Key == accountDto.SessionId); + public Task LogInAsync(AccountDto accountDto) + { + var invalidTryAccount = _invalidTries.FirstOrDefault(x => x.Key == accountDto.Login); - if (invalidTryAccount.Value >= 2) + if (invalidTryAccount.Value >= 2) + { + if ((DateTime.Now - _lastTimes + .FirstOrDefault(x => x.Key == accountDto.Login).Value) + .TotalSeconds >= CoolDownTime) { - if ((DateTime.Now - _lastTimes.FirstOrDefault(x => x.Key == accountDto.SessionId).Value) - .TotalSeconds >= CoolDownTime) - { - _invalidTries.TryRemove(invalidTryAccount); - } - else - { - // ReSharper disable once RedundantAssignment - _lastTimes.AddOrUpdate(accountDto.SessionId, accountDto.LastRequest, - ((s, time) => time = accountDto.LastRequest)); - throw new LoginCooldownException("CoolDown", CoolDownTime); - } + _invalidTries.TryRemove(invalidTryAccount); } - - var login = _deserializedObject.ConcurrentDictionary.Values - .FirstOrDefault(x => x.Login == accountDto.Login && x.Password == accountDto.Password); - - if (login == null) + else { - _invalidTries.AddOrUpdate(accountDto.SessionId, 1, (s, i) => i + 1); - - _lastTimes.AddOrUpdate(accountDto.SessionId, accountDto.LastRequest, + _lastTimes.AddOrUpdate(accountDto.Login, accountDto.LastRequest, ((s, time) => time = accountDto.LastRequest)); - - throw new InvalidCredentialsException($"{accountDto.Login}"); + throw new LoginCooldownException("CoolDown", CoolDownTime); } + } - if (AccountsActive.Any(x => x.Value == login) - || AccountsActive.ContainsKey(accountDto.SessionId)) + var login = _deserializedObject.ConcurrentDictionary.Values + .FirstOrDefault(x => x.Login == accountDto.Login && x.Password == accountDto.Password); - { - var thisAccount = AccountsActive.FirstOrDefault(x => x.Key == accountDto.SessionId).Value; - AccountsActive.TryUpdate(accountDto.SessionId, login, thisAccount); - //throw new UserAlreadySignedInException(nameof(login.Login)); - } + if (login == null) + { + _invalidTries.AddOrUpdate(accountDto.Login, 1, (s, i) => i + 1); - AccountsActive.TryAdd(accountDto.SessionId, login); - _logger.LogTrace(""); //todo + _lastTimes.AddOrUpdate(accountDto.Login, accountDto.LastRequest, + ((s, time) => time = accountDto.LastRequest)); - return login; - }); + throw new InvalidCredentialsException($"{accountDto.Login}"); + } - return await tasks; + if (AccountsActive.Any(x => x.Value == login)) + { + throw new UserAlreadySignedInException(nameof(login.Login)); + } + + var sessionId = Guid.NewGuid().ToString(); + AccountsActive.TryAdd(sessionId, login); + _logger.LogTrace($"{sessionId} to {login}"); //todo + + return Task.FromResult(sessionId); } /// @@ -105,7 +97,6 @@ public async Task LogOutAsync(string sessionId) .StartNew(() => AccountsActive.ContainsKey(sessionId) && AccountsActive.TryRemove(sessionId, out _)); return await tasks; - } /// @@ -116,7 +107,6 @@ public async Task LogOutAsync(string sessionId) public async Task IsActive(string sessionId) { var tasks = Task.Factory.StartNew(() => AccountsActive.ContainsKey(sessionId)); - return await tasks; } diff --git a/Server/Services/Interfaces/IAccountManager.cs b/Server/Services/Interfaces/IAccountManager.cs index 73fdea6..f20f482 100644 --- a/Server/Services/Interfaces/IAccountManager.cs +++ b/Server/Services/Interfaces/IAccountManager.cs @@ -21,7 +21,7 @@ public interface IAccountManager /// When too many false retries /// When invalid data /// When used is already signed in - Task LogInAsync(AccountDto accountDto); + Task LogInAsync(AccountDto accountDto); /// /// Async method to sign out of account diff --git a/Server/Startup.cs b/Server/Startup.cs index bfe807e..e674ff5 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -45,6 +45,7 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthorization(); From 65dc9712c6d09a70638d48fe8039fa8b02361f21 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 14 May 2021 19:32:01 +0300 Subject: [PATCH 05/56] Splitting project into layers --- RockPaperScissorsGame.sln | 12 + .../Exceptions}/AlreadyExistsException.cs | 0 .../InvalidCredentialsException.cs | 0 .../Exceptions}/LoginCooldownException.cs | 0 .../Exceptions}/LoginErrorException.cs | 0 .../TwinkGameRoomCreationException.cs | 0 .../Exceptions}/UnknownReasonException.cs | 0 .../UserAlreadySignedInException.cs | 0 .../Exceptions}/UserNotFoundException.cs | 0 Server.Bll/Server.Bll.csproj | 11 + .../Services/AccountManager.cs | 0 .../Services/DeserializedObject.cs | 0 .../Services/Interfaces/IAccountManager.cs | 0 .../Interfaces/IDeserializedObject.cs | 0 .../Services}/Interfaces/IRoomCoordinator.cs | 0 .../Services}/Interfaces/IRoundCoordinator.cs | 0 .../Services/Interfaces/IStorage.cs | 0 .../Services}/RockPaperScissors.cs | 0 .../Services}/RoomCoordinator.cs | 0 .../Services}/RoundCoordinator.cs | 0 {Server => Server.Bll}/Services/Storage.cs | 0 Server.Dal/Context/ServerContext.cs | 17 ++ Server.Dal/Entities/Account.cs | 42 +++ .../Models => Server.Dal/Entities}/Room.cs | 26 +- Server.Dal/Entities/RoomPlayers.cs | 31 ++ Server.Dal/Entities/Round.cs | 35 +++ .../Entities}/Statistics.cs | 37 ++- Server.Dal/IRepository.cs | 18 ++ .../20210509231534_InitialCommit.Designer.cs | 249 ++++++++++++++++ .../20210509231534_InitialCommit.cs | 228 +++++++++++++++ .../20210510122522_FixEntities.Designer.cs | 265 +++++++++++++++++ .../Migrations/20210510122522_FixEntities.cs | 55 ++++ .../20210514162359_PlayerMoves.Designer.cs | 271 ++++++++++++++++++ .../Migrations/20210514162359_PlayerMoves.cs | 35 +++ .../Migrations/ServerContextModelSnapshot.cs | 269 +++++++++++++++++ Server.Dal/Repository.cs | 71 +++++ Server.Dal/Server.Dal.csproj | 16 ++ Server/Account | 2 +- Server/Contracts/AccountDto.cs | 8 +- Server/Extensions/LoggingMiddleware.cs | 1 + Server/GameLogic/Models/Interfaces/IRoom.cs | 19 -- Server/GameLogic/Models/Interfaces/IRound.cs | 31 -- Server/GameLogic/Models/RequiredGameMove.cs | 13 - Server/GameLogic/Models/Round.cs | 51 ---- Server/Mappings/StatisticsMappings.cs | 26 -- Server/Models/Account.cs | 26 -- Server/Models/Interfaces/IAccount.cs | 18 -- Server/Models/Interfaces/IStatistics.cs | 38 --- Server/Round | 0 Server/Server.csproj | 8 + Server/Startup.cs | 29 +- Server/Statistics | 2 +- Server/appsettings.json | 3 + 53 files changed, 1700 insertions(+), 263 deletions(-) rename {Server/Exceptions/Register => Server.Bll/Exceptions}/AlreadyExistsException.cs (100%) rename {Server/Exceptions/LogIn => Server.Bll/Exceptions}/InvalidCredentialsException.cs (100%) rename {Server/Exceptions/LogIn => Server.Bll/Exceptions}/LoginCooldownException.cs (100%) rename {Server/Exceptions/LogIn => Server.Bll/Exceptions}/LoginErrorException.cs (100%) rename {Server/GameLogic => Server.Bll}/Exceptions/TwinkGameRoomCreationException.cs (100%) rename {Server/Exceptions/Register => Server.Bll/Exceptions}/UnknownReasonException.cs (100%) rename {Server/Exceptions/LogIn => Server.Bll/Exceptions}/UserAlreadySignedInException.cs (100%) rename {Server/Exceptions/LogIn => Server.Bll/Exceptions}/UserNotFoundException.cs (100%) create mode 100644 Server.Bll/Server.Bll.csproj rename {Server => Server.Bll}/Services/AccountManager.cs (100%) rename {Server => Server.Bll}/Services/DeserializedObject.cs (100%) rename {Server => Server.Bll}/Services/Interfaces/IAccountManager.cs (100%) rename {Server => Server.Bll}/Services/Interfaces/IDeserializedObject.cs (100%) rename {Server/GameLogic/LogicServices => Server.Bll/Services}/Interfaces/IRoomCoordinator.cs (100%) rename {Server/GameLogic/LogicServices => Server.Bll/Services}/Interfaces/IRoundCoordinator.cs (100%) rename {Server => Server.Bll}/Services/Interfaces/IStorage.cs (100%) rename {Server/GameLogic => Server.Bll/Services}/RockPaperScissors.cs (100%) rename {Server/GameLogic/LogicServices => Server.Bll/Services}/RoomCoordinator.cs (100%) rename {Server/GameLogic/LogicServices => Server.Bll/Services}/RoundCoordinator.cs (100%) rename {Server => Server.Bll}/Services/Storage.cs (100%) create mode 100644 Server.Dal/Context/ServerContext.cs create mode 100644 Server.Dal/Entities/Account.cs rename {Server/GameLogic/Models => Server.Dal/Entities}/Room.cs (65%) create mode 100644 Server.Dal/Entities/RoomPlayers.cs create mode 100644 Server.Dal/Entities/Round.cs rename {Server/Models => Server.Dal/Entities}/Statistics.cs (57%) create mode 100644 Server.Dal/IRepository.cs create mode 100644 Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs create mode 100644 Server.Dal/Migrations/20210509231534_InitialCommit.cs create mode 100644 Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs create mode 100644 Server.Dal/Migrations/20210510122522_FixEntities.cs create mode 100644 Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs create mode 100644 Server.Dal/Migrations/20210514162359_PlayerMoves.cs create mode 100644 Server.Dal/Migrations/ServerContextModelSnapshot.cs create mode 100644 Server.Dal/Repository.cs create mode 100644 Server.Dal/Server.Dal.csproj delete mode 100644 Server/GameLogic/Models/Interfaces/IRoom.cs delete mode 100644 Server/GameLogic/Models/Interfaces/IRound.cs delete mode 100644 Server/GameLogic/Models/RequiredGameMove.cs delete mode 100644 Server/GameLogic/Models/Round.cs delete mode 100644 Server/Mappings/StatisticsMappings.cs delete mode 100644 Server/Models/Account.cs delete mode 100644 Server/Models/Interfaces/IAccount.cs delete mode 100644 Server/Models/Interfaces/IStatistics.cs delete mode 100644 Server/Round diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 8d353c0..487c114 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{B6D69162-F9E0-4165-A288-00FF1D073291}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Dal", "Server.Dal\Server.Dal.csproj", "{1207E8F7-D6FF-4F7D-B5A1-55D96F286430}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server.Bll\Server.Bll.csproj", "{FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B6D69162-F9E0-4165-A288-00FF1D073291}.Debug|Any CPU.Build.0 = Debug|Any CPU {B6D69162-F9E0-4165-A288-00FF1D073291}.Release|Any CPU.ActiveCfg = Release|Any CPU {B6D69162-F9E0-4165-A288-00FF1D073291}.Release|Any CPU.Build.0 = Release|Any CPU + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Release|Any CPU.Build.0 = Release|Any CPU + {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Server/Exceptions/Register/AlreadyExistsException.cs b/Server.Bll/Exceptions/AlreadyExistsException.cs similarity index 100% rename from Server/Exceptions/Register/AlreadyExistsException.cs rename to Server.Bll/Exceptions/AlreadyExistsException.cs diff --git a/Server/Exceptions/LogIn/InvalidCredentialsException.cs b/Server.Bll/Exceptions/InvalidCredentialsException.cs similarity index 100% rename from Server/Exceptions/LogIn/InvalidCredentialsException.cs rename to Server.Bll/Exceptions/InvalidCredentialsException.cs diff --git a/Server/Exceptions/LogIn/LoginCooldownException.cs b/Server.Bll/Exceptions/LoginCooldownException.cs similarity index 100% rename from Server/Exceptions/LogIn/LoginCooldownException.cs rename to Server.Bll/Exceptions/LoginCooldownException.cs diff --git a/Server/Exceptions/LogIn/LoginErrorException.cs b/Server.Bll/Exceptions/LoginErrorException.cs similarity index 100% rename from Server/Exceptions/LogIn/LoginErrorException.cs rename to Server.Bll/Exceptions/LoginErrorException.cs diff --git a/Server/GameLogic/Exceptions/TwinkGameRoomCreationException.cs b/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs similarity index 100% rename from Server/GameLogic/Exceptions/TwinkGameRoomCreationException.cs rename to Server.Bll/Exceptions/TwinkGameRoomCreationException.cs diff --git a/Server/Exceptions/Register/UnknownReasonException.cs b/Server.Bll/Exceptions/UnknownReasonException.cs similarity index 100% rename from Server/Exceptions/Register/UnknownReasonException.cs rename to Server.Bll/Exceptions/UnknownReasonException.cs diff --git a/Server/Exceptions/LogIn/UserAlreadySignedInException.cs b/Server.Bll/Exceptions/UserAlreadySignedInException.cs similarity index 100% rename from Server/Exceptions/LogIn/UserAlreadySignedInException.cs rename to Server.Bll/Exceptions/UserAlreadySignedInException.cs diff --git a/Server/Exceptions/LogIn/UserNotFoundException.cs b/Server.Bll/Exceptions/UserNotFoundException.cs similarity index 100% rename from Server/Exceptions/LogIn/UserNotFoundException.cs rename to Server.Bll/Exceptions/UserNotFoundException.cs diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj new file mode 100644 index 0000000..9db0afc --- /dev/null +++ b/Server.Bll/Server.Bll.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/Server/Services/AccountManager.cs b/Server.Bll/Services/AccountManager.cs similarity index 100% rename from Server/Services/AccountManager.cs rename to Server.Bll/Services/AccountManager.cs diff --git a/Server/Services/DeserializedObject.cs b/Server.Bll/Services/DeserializedObject.cs similarity index 100% rename from Server/Services/DeserializedObject.cs rename to Server.Bll/Services/DeserializedObject.cs diff --git a/Server/Services/Interfaces/IAccountManager.cs b/Server.Bll/Services/Interfaces/IAccountManager.cs similarity index 100% rename from Server/Services/Interfaces/IAccountManager.cs rename to Server.Bll/Services/Interfaces/IAccountManager.cs diff --git a/Server/Services/Interfaces/IDeserializedObject.cs b/Server.Bll/Services/Interfaces/IDeserializedObject.cs similarity index 100% rename from Server/Services/Interfaces/IDeserializedObject.cs rename to Server.Bll/Services/Interfaces/IDeserializedObject.cs diff --git a/Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs b/Server.Bll/Services/Interfaces/IRoomCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/Interfaces/IRoomCoordinator.cs rename to Server.Bll/Services/Interfaces/IRoomCoordinator.cs diff --git a/Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs b/Server.Bll/Services/Interfaces/IRoundCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/Interfaces/IRoundCoordinator.cs rename to Server.Bll/Services/Interfaces/IRoundCoordinator.cs diff --git a/Server/Services/Interfaces/IStorage.cs b/Server.Bll/Services/Interfaces/IStorage.cs similarity index 100% rename from Server/Services/Interfaces/IStorage.cs rename to Server.Bll/Services/Interfaces/IStorage.cs diff --git a/Server/GameLogic/RockPaperScissors.cs b/Server.Bll/Services/RockPaperScissors.cs similarity index 100% rename from Server/GameLogic/RockPaperScissors.cs rename to Server.Bll/Services/RockPaperScissors.cs diff --git a/Server/GameLogic/LogicServices/RoomCoordinator.cs b/Server.Bll/Services/RoomCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/RoomCoordinator.cs rename to Server.Bll/Services/RoomCoordinator.cs diff --git a/Server/GameLogic/LogicServices/RoundCoordinator.cs b/Server.Bll/Services/RoundCoordinator.cs similarity index 100% rename from Server/GameLogic/LogicServices/RoundCoordinator.cs rename to Server.Bll/Services/RoundCoordinator.cs diff --git a/Server/Services/Storage.cs b/Server.Bll/Services/Storage.cs similarity index 100% rename from Server/Services/Storage.cs rename to Server.Bll/Services/Storage.cs diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs new file mode 100644 index 0000000..e56db10 --- /dev/null +++ b/Server.Dal/Context/ServerContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Server.Dal.Entities; + +namespace Server.Dal.Context +{ + public class ServerContext : DbContext + { + public DbSet Accounts { get; set; } + public DbSet Rooms { get; set; } + public DbSet RoomPlayersEnumerable { get; set; } + public DbSet Rounds { get; set; } + public DbSet StatisticsEnumerable { get; set; } + + public ServerContext(DbContextOptions contextOptions) + :base(contextOptions) { } + } +} \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs new file mode 100644 index 0000000..1cc2c91 --- /dev/null +++ b/Server.Dal/Entities/Account.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities +{ + [Table("Accounts")] + public class Account + { + /// + /// Id of account. Unique to everyone and similar with Statistics Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } + + /// + /// Nick name of Account + /// + public string Login { get; set; } + + /// + /// Password of the Account + /// + public string Password { get; set; } + + /// + /// Statistics id, connected to this account + /// + public string StatisticsId { get; set; } + + /// + /// Linked to this player statistics + /// + [ForeignKey("StatisticsId")] + public virtual Statistics Statistics { get; set; } + + public int RoomPlayerId { get; set; } + + [ForeignKey("RoomPlayerId")] + public virtual RoomPlayers RoomPlayers { get; set; } + } +} \ No newline at end of file diff --git a/Server/GameLogic/Models/Room.cs b/Server.Dal/Entities/Room.cs similarity index 65% rename from Server/GameLogic/Models/Room.cs rename to Server.Dal/Entities/Room.cs index ac8425e..1a87d63 100644 --- a/Server/GameLogic/Models/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -1,26 +1,34 @@ using System; -using System.Collections.Concurrent; -using Server.GameLogic.Models.Interfaces; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; -namespace Server.GameLogic.Models +namespace Server.Dal.Entities { - public class Room : IRoom + [Table("Rooms")] + public class Room { /// /// Id of the room. Consists of 5 randomized chars /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] public string RoomId { get; set; } /// - /// ConcurrentDictionary of all players. - /// Where string - account Id. Bool - flag is he ready + /// Id of current round /// - public ConcurrentDictionary Players { get; set; } + public string RoundId { get; set; } /// - /// Id of current round + /// Round, linked to this room /// - public string CurrentRoundId { get; set; } + [ForeignKey("RoundId")] + public virtual Round Round { get; set; } + + public int RoomPlayerId { get; set; } + + [ForeignKey("RoomPlayerId")] + public virtual RoomPlayers RoomPlayers { get; set; } /// /// Flag is this room is private diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs new file mode 100644 index 0000000..a327bd3 --- /dev/null +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.CodeAnalysis; + +namespace Server.Dal.Entities +{ + [Table("RoomPlayers")] + public class RoomPlayers + { + [Key] + public int Id { get; set; } + + public string RoomId { get; set; } + + [ForeignKey("RoomId")] + public virtual Room Room { get; set; } + + public virtual ICollection Accounts { get; set; } + + public int FirstPlayerMove { get; set; } + + public int SecondPlayerMove { get; set; } + + public string RoundId { get; set; } + + [ForeignKey("RoundId")] + [AllowNull] + public virtual Round Round { get; set; } + } +} \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs new file mode 100644 index 0000000..e720c07 --- /dev/null +++ b/Server.Dal/Entities/Round.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities +{ + [Table("Rounds")] + public class Round + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } + + public int RoomPlayersId { get; set; } + + [ForeignKey("RoomPlayersId")] + public virtual RoomPlayers RoomPlayers { get; set; } + + public bool IsFinished { get; set; } + + public DateTime TimeFinished { get; set; } + + public string WinnerId { get; set; } + + [ForeignKey("WinnerId")] + public virtual Account Winner { get; set; } + + public string LoserId { get; set; } + + [ForeignKey("LoserId")] + public virtual Account Loser { get; set; } + + } +} diff --git a/Server/Models/Statistics.cs b/Server.Dal/Entities/Statistics.cs similarity index 57% rename from Server/Models/Statistics.cs rename to Server.Dal/Entities/Statistics.cs index 526bcee..0313c81 100644 --- a/Server/Models/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -1,39 +1,50 @@ -using Server.Models.Interfaces; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Models +namespace Server.Dal.Entities { - public class Statistics : IStatistics + [Table("Statistics")] + public class Statistics { /// /// Id of statistics. Equivalent to Account Id /// + + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] public string Id { get; set; } /// - /// Nick name of Account + /// Id of linked account + /// + public string AccountId { get; set; } + + /// + /// Linked account /// - public string Login { get; set; } + [ForeignKey("AccountId")] + public virtual Account Account { get; set; } /// /// Total amount of Wins /// - public int Wins { get; set; } + public int? Wins { get; set; } /// /// Total amount of Loses /// - public int Loss { get; set; } + public int? Loss { get; set; } /// /// Total amount of Draws. OBSOLETE /// - public int Draws { get; set; } + public int? Draws { get; set; } /// /// Ratio Wins to Losses. Win/Loss * 100 /// - public double WinLossRatio { get; set; } + public double? WinLossRatio { get; set; } /// /// Ratio for the last 7 days @@ -43,22 +54,22 @@ public class Statistics : IStatistics /// /// Times used rock /// - public int UsedRock { get; set; } + public int? UsedRock { get; set; } /// /// Times used Paper /// - public int UsedPaper { get; set; } + public int? UsedPaper { get; set; } /// /// Times used Scissors /// - public int UsedScissors { get; set; } + public int? UsedScissors { get; set; } /// /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// - public int Score { get; set; } + public int? Score { get; set; } diff --git a/Server.Dal/IRepository.cs b/Server.Dal/IRepository.cs new file mode 100644 index 0000000..9519d04 --- /dev/null +++ b/Server.Dal/IRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Server.Dal +{ + public interface IRepository : IDisposable + { + ValueTask GetById(string id); + Task> GetAll(); + TEntity Add(TEntity entity); + TEntity Remove(TEntity entity); + void Update(TEntity entity); + Task SaveAsync(); + void EnsureCreated(); + } +} \ No newline at end of file diff --git a/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs b/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs new file mode 100644 index 0000000..41804c9 --- /dev/null +++ b/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs @@ -0,0 +1,249 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210509231534_InitialCommit")] + partial class InitialCommit + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatisticsId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("RoomId"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinished") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayersId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany("Accounts") + .HasForeignKey("RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithMany() + .HasForeignKey("RoomId"); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("Room"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210509231534_InitialCommit.cs b/Server.Dal/Migrations/20210509231534_InitialCommit.cs new file mode 100644 index 0000000..d1d1b67 --- /dev/null +++ b/Server.Dal/Migrations/20210509231534_InitialCommit.cs @@ -0,0 +1,228 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class InitialCommit : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Statistics", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AccountId = table.Column(type: "TEXT", nullable: true), + Wins = table.Column(type: "INTEGER", nullable: true), + Loss = table.Column(type: "INTEGER", nullable: true), + Draws = table.Column(type: "INTEGER", nullable: true), + WinLossRatio = table.Column(type: "REAL", nullable: true), + TimeSpent = table.Column(type: "TEXT", nullable: true), + UsedRock = table.Column(type: "INTEGER", nullable: true), + UsedPaper = table.Column(type: "INTEGER", nullable: true), + UsedScissors = table.Column(type: "INTEGER", nullable: true), + Score = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Statistics", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Accounts", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Login = table.Column(type: "TEXT", nullable: true), + Password = table.Column(type: "TEXT", nullable: true), + StatisticsId = table.Column(type: "TEXT", nullable: true), + RoomPlayerId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Accounts", x => x.Id); + table.ForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + column: x => x.StatisticsId, + principalTable: "Statistics", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Rooms", + columns: table => new + { + RoomId = table.Column(type: "TEXT", nullable: false), + RoundId = table.Column(type: "TEXT", nullable: true), + RoomPlayerId = table.Column(type: "INTEGER", nullable: false), + IsPrivate = table.Column(type: "INTEGER", nullable: false), + IsReady = table.Column(type: "INTEGER", nullable: false), + IsFull = table.Column(type: "INTEGER", nullable: false), + CreationTime = table.Column(type: "TEXT", nullable: false), + IsRoundEnded = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rooms", x => x.RoomId); + }); + + migrationBuilder.CreateTable( + name: "Rounds", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RoomPlayersId = table.Column(type: "INTEGER", nullable: false), + IsFinished = table.Column(type: "INTEGER", nullable: false), + TimeFinished = table.Column(type: "TEXT", nullable: false), + WinnerId = table.Column(type: "TEXT", nullable: true), + LoserId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Rounds", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoomPlayers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "TEXT", nullable: true), + RoundId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomPlayers", x => x.Id); + table.ForeignKey( + name: "FK_RoomPlayers_Rooms_RoomId", + column: x => x.RoomId, + principalTable: "Rooms", + principalColumn: "RoomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_RoomPlayers_Rounds_RoundId", + column: x => x.RoundId, + principalTable: "Rounds", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_RoomPlayerId", + table: "Accounts", + column: "RoomPlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_StatisticsId", + table: "Accounts", + column: "StatisticsId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_RoomId", + table: "RoomPlayers", + column: "RoomId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_RoundId", + table: "RoomPlayers", + column: "RoundId"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_RoomPlayerId", + table: "Rooms", + column: "RoomPlayerId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_RoundId", + table: "Rooms", + column: "RoundId"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId"); + + migrationBuilder.CreateIndex( + name: "IX_Statistics_AccountId", + table: "Statistics", + column: "AccountId"); + + migrationBuilder.AddForeignKey( + name: "FK_Statistics_Accounts_AccountId", + table: "Statistics", + column: "AccountId", + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Accounts_RoomPlayers_RoomPlayerId", + table: "Accounts", + column: "RoomPlayerId", + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Rooms_RoomPlayers_RoomPlayerId", + table: "Rooms", + column: "RoomPlayerId", + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Rooms_Rounds_RoundId", + table: "Rooms", + column: "RoundId", + principalTable: "Rounds", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId", + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Accounts_RoomPlayers_RoomPlayerId", + table: "Accounts"); + + migrationBuilder.DropForeignKey( + name: "FK_Rooms_RoomPlayers_RoomPlayerId", + table: "Rooms"); + + migrationBuilder.DropForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds"); + + migrationBuilder.DropForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + table: "Accounts"); + + migrationBuilder.DropTable( + name: "RoomPlayers"); + + migrationBuilder.DropTable( + name: "Rooms"); + + migrationBuilder.DropTable( + name: "Rounds"); + + migrationBuilder.DropTable( + name: "Statistics"); + + migrationBuilder.DropTable( + name: "Accounts"); + } + } +} diff --git a/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs b/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs new file mode 100644 index 0000000..961a78a --- /dev/null +++ b/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs @@ -0,0 +1,265 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210510122522_FixEntities")] + partial class FixEntities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatisticsId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("RoomId"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinished") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany("Accounts") + .HasForeignKey("RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithMany() + .HasForeignKey("RoomId"); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("Room"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210510122522_FixEntities.cs b/Server.Dal/Migrations/20210510122522_FixEntities.cs new file mode 100644 index 0000000..e9a55bd --- /dev/null +++ b/Server.Dal/Migrations/20210510122522_FixEntities.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class FixEntities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Rounds_LoserId", + table: "Rounds", + column: "LoserId"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_WinnerId", + table: "Rounds", + column: "WinnerId"); + + migrationBuilder.AddForeignKey( + name: "FK_Rounds_Accounts_LoserId", + table: "Rounds", + column: "LoserId", + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds", + column: "WinnerId", + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rounds_Accounts_LoserId", + table: "Rounds"); + + migrationBuilder.DropForeignKey( + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds"); + + migrationBuilder.DropIndex( + name: "IX_Rounds_LoserId", + table: "Rounds"); + + migrationBuilder.DropIndex( + name: "IX_Rounds_WinnerId", + table: "Rounds"); + } + } +} diff --git a/Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs b/Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs new file mode 100644 index 0000000..42c4755 --- /dev/null +++ b/Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210514162359_PlayerMoves")] + partial class PlayerMoves + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatisticsId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("RoomId"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinished") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany("Accounts") + .HasForeignKey("RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithMany() + .HasForeignKey("RoomId"); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("Room"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210514162359_PlayerMoves.cs b/Server.Dal/Migrations/20210514162359_PlayerMoves.cs new file mode 100644 index 0000000..4ee47e5 --- /dev/null +++ b/Server.Dal/Migrations/20210514162359_PlayerMoves.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class PlayerMoves : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FirstPlayerMove", + table: "RoomPlayers", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "SecondPlayerMove", + table: "RoomPlayers", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FirstPlayerMove", + table: "RoomPlayers"); + + migrationBuilder.DropColumn( + name: "SecondPlayerMove", + table: "RoomPlayers"); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs new file mode 100644 index 0000000..047371f --- /dev/null +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -0,0 +1,269 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + partial class ServerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("StatisticsId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("RoomId"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinished") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany("Accounts") + .HasForeignKey("RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithMany() + .HasForeignKey("RoomId"); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("Room"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Repository.cs b/Server.Dal/Repository.cs new file mode 100644 index 0000000..373948f --- /dev/null +++ b/Server.Dal/Repository.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Server.Dal.Context; + +namespace Server.Dal +{ + public class Repository :IRepository where TEntity: class, new() + { + private bool _disposed; + private ServerContext ServerContext { get;} + + public Repository(ServerContext serverContext) + { + ServerContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); + } + public virtual async void Dispose() + { + await Dispose(true); + GC.SuppressFinalize(this); + } + + public async ValueTask GetById(string id) + { + return await ServerContext.FindAsync(id); + } + + public async Task> GetAll() + { + return await Task.FromResult(ServerContext.Set().AsQueryable()); + } + + public TEntity Add(TEntity entity) + { + var addEntityTask = ServerContext.AddAsync(entity); + + return addEntityTask.IsCompletedSuccessfully + ? addEntityTask.Result.Entity : null; + } + + public TEntity Remove(TEntity entity) + { + return ServerContext.Remove(entity).Entity; + } + + public void Update(TEntity entity) + { + ServerContext.Entry(entity).State = EntityState.Modified; + } + + public async Task SaveAsync() + { + return await ServerContext.SaveChangesAsync(); + } + + public void EnsureCreated() + { + throw new System.NotImplementedException(); + } + + private async Task Dispose(bool dispose) + { + if (!dispose || _disposed) + return; + _disposed = true; + await ServerContext.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/Server.Dal/Server.Dal.csproj b/Server.Dal/Server.Dal.csproj new file mode 100644 index 0000000..cc16374 --- /dev/null +++ b/Server.Dal/Server.Dal.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Server/Account b/Server/Account index abbc85c..b15c5e9 100644 --- a/Server/Account +++ b/Server/Account @@ -1 +1 @@ -{"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Password":"123123"}} \ No newline at end of file +{"1a047fbd-901f-464f-ab6b-e6d0f46f1558":{"Id":"1a047fbd-901f-464f-ab6b-e6d0f46f1558","Login":"string","Password":"string"},"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Password":"123123"}} \ No newline at end of file diff --git a/Server/Contracts/AccountDto.cs b/Server/Contracts/AccountDto.cs index d68bf22..c467efb 100644 --- a/Server/Contracts/AccountDto.cs +++ b/Server/Contracts/AccountDto.cs @@ -3,13 +3,13 @@ namespace Server.Contracts { - public class AccountDto + public record AccountDto { [Required(ErrorMessage = "Login is required!")] - public string Login { get; init; } + public string Login { get; set; } [Required(ErrorMessage = "Password is required!!")] [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] - public string Password { get; init; } - public DateTime LastRequest { get; set; } + public string Password { get; set;} + public DateTime LastRequest { get; set;} } } \ No newline at end of file diff --git a/Server/Extensions/LoggingMiddleware.cs b/Server/Extensions/LoggingMiddleware.cs index 17a2d8b..595d451 100644 --- a/Server/Extensions/LoggingMiddleware.cs +++ b/Server/Extensions/LoggingMiddleware.cs @@ -38,6 +38,7 @@ public async Task Invoke(HttpContext context) { var requestInformation = $"Request information:\n" + $"Schema:{context.Request.Scheme}\n" + + $"Content-Type:{context.Request.ContentType}" + $"Host:{context.Request.Host}\n" + $"Path:{context.Request.Path}\n" + $"QueryString:{context.Request.QueryString}\n" + diff --git a/Server/GameLogic/Models/Interfaces/IRoom.cs b/Server/GameLogic/Models/Interfaces/IRoom.cs deleted file mode 100644 index 7c54c28..0000000 --- a/Server/GameLogic/Models/Interfaces/IRoom.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Concurrent; -using Newtonsoft.Json; - -namespace Server.GameLogic.Models.Interfaces -{ - public interface IRoom - { - [JsonProperty("RoomId")] - string RoomId { get; set; } - [JsonProperty("Players")] - ConcurrentDictionary Players { get; set; } - [JsonProperty("CurrentRoundId")] - string CurrentRoundId { get; set; } - [JsonProperty("CreationTime")] - DateTime CreationTime { get; set; } - } - -} diff --git a/Server/GameLogic/Models/Interfaces/IRound.cs b/Server/GameLogic/Models/Interfaces/IRound.cs deleted file mode 100644 index abee617..0000000 --- a/Server/GameLogic/Models/Interfaces/IRound.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Text.Json.Serialization; - -namespace Server.GameLogic.Models.Interfaces -{ - public interface IRound - { - [JsonPropertyName("Id")] - string Id { get; init; } - - [JsonPropertyName("RoomId")] - string RoomId { get; set; } - - [JsonPropertyName("Moves")] - public ConcurrentDictionary PlayerMoves { get; set; } - - [JsonPropertyName("IsFinished")] - bool IsFinished { get; set; } - - [JsonPropertyName("TimeFinished")] - DateTime TimeFinished { get; set; } - - [JsonPropertyName("WinnerId")] - string WinnerId { get; set; } - - [JsonPropertyName("LoserId")] - string LoserId { get; set; } - public bool IsDraw { get; set; } - } -} diff --git a/Server/GameLogic/Models/RequiredGameMove.cs b/Server/GameLogic/Models/RequiredGameMove.cs deleted file mode 100644 index bcf6cd4..0000000 --- a/Server/GameLogic/Models/RequiredGameMove.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Server.GameLogic.Models -{ - /// - /// Enumerator of available player operations - /// - public enum RequiredGameMove - { - Default = 0, - Rock = 1, - Paper = 2, - Scissors = 3 - } -} diff --git a/Server/GameLogic/Models/Round.cs b/Server/GameLogic/Models/Round.cs deleted file mode 100644 index bd4ab85..0000000 --- a/Server/GameLogic/Models/Round.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Concurrent; -using Server.GameLogic.Models.Interfaces; - -namespace Server.GameLogic.Models -{ - public class Round : IRound - { - /// - /// Id of current round. GUID - /// - public string Id { get; init; } - - /// - /// Id of room, Where this round is situated. OBSOLETE - /// - - public string RoomId { get; set; } - - /// - /// Flag is this round has ended - /// - public bool IsFinished { get; set; } - - /// - /// Dictionary of player Id and his move - /// - public ConcurrentDictionary PlayerMoves { get; set; } - - /// - /// Is the result is draw - /// - public bool IsDraw { get; set; } - - /// - /// Time of finishing this room. Also used to check 20 seconds for the move. - /// - public DateTime TimeFinished { get; set; } - - /// - /// Login of winner Id - /// - public string WinnerId { get; set; } - - /// - /// Login of loser Id - /// - public string LoserId { get; set; } - - } -} diff --git a/Server/Mappings/StatisticsMappings.cs b/Server/Mappings/StatisticsMappings.cs deleted file mode 100644 index 82aa919..0000000 --- a/Server/Mappings/StatisticsMappings.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Server.Contracts; -using Server.Models; - -namespace Server.Mappings -{ - public static class StatisticsMappings - { - /// - /// Method to map Big statistics to a small overall statistics - /// - /// Big statistics of all accounts - /// StatisticsDto - public static StatisticsDto ToStatisticsDto(this Statistics statistics) - { - return statistics == null - ? null - : new StatisticsDto - { - Login = statistics.Login, - Score = statistics.Score - - }; - - } - } -} \ No newline at end of file diff --git a/Server/Models/Account.cs b/Server/Models/Account.cs deleted file mode 100644 index 2e13543..0000000 --- a/Server/Models/Account.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Server.Models.Interfaces; - -namespace Server.Models -{ - public class Account : IAccount - { - - /// - /// Id of account. Unique to everyone and similar with Statistics Id - /// - public string Id { get; init; } - - /// - /// Nick name of Account - /// - public string Login { get; set; } - - /// - /// Password of the Account - /// - [StringLength(20, MinimumLength=5, ErrorMessage = "Invalid password length")] - public string Password { get; set; } - - } -} \ No newline at end of file diff --git a/Server/Models/Interfaces/IAccount.cs b/Server/Models/Interfaces/IAccount.cs deleted file mode 100644 index dd2d657..0000000 --- a/Server/Models/Interfaces/IAccount.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; -namespace Server.Models.Interfaces -{ - public interface IAccount - { - [JsonPropertyName("Id")] - string Id { get; } - - [JsonPropertyName("Login")] - string Login { get; set; } - - [JsonPropertyName("Password")] - string Password { get; set; } - - - - } -} \ No newline at end of file diff --git a/Server/Models/Interfaces/IStatistics.cs b/Server/Models/Interfaces/IStatistics.cs deleted file mode 100644 index f6d05c7..0000000 --- a/Server/Models/Interfaces/IStatistics.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Server.Models.Interfaces -{ - public interface IStatistics - { - [JsonPropertyName("Id")] - string Id { get; set; } - - [JsonPropertyName("UserLogin")] - string Login { get; set; } - - [JsonPropertyName("Wins")] - int Wins { get; set; } - - [JsonPropertyName("Loss")] - int Loss { get; set; } - - [JsonPropertyName("WinToLossRatio")] - double WinLossRatio { get; set; } - - [JsonPropertyName("TimeSpent")] - string TimeSpent { get; set; } - - [JsonPropertyName("UsedRock")] - int UsedRock { get; set; } - - [JsonPropertyName("UsedPaper")] - int UsedPaper { get; set; } - - [JsonPropertyName("UsedScissors")] - int UsedScissors { get; set; } - - [JsonPropertyName("Score")] - int Score { get; set; } - - } -} \ No newline at end of file diff --git a/Server/Round b/Server/Round deleted file mode 100644 index e69de29..0000000 diff --git a/Server/Server.csproj b/Server/Server.csproj index 5c96d0a..1d73ec0 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -5,6 +5,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -19,5 +23,9 @@ + + + + diff --git a/Server/Startup.cs b/Server/Startup.cs index e674ff5..cdbddf2 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -1,15 +1,12 @@ -using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; +using Server.Dal.Context; using Server.Extensions; -using Server.GameLogic.LogicServices; -using Server.Services; -using Server.Services.Interfaces; namespace Server { @@ -21,19 +18,22 @@ public Startup(IConfiguration configuration) } private IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public static void ConfigureServices(IServiceCollection services) + + public void ConfigureServices(IServiceCollection services) { - services.AddServices(); + services.AddServices() + .AddDbContext( + builder => builder.UseSqlite + (Configuration.GetConnectionString("connectionString"), + x=> x.MigrationsAssembly("Server.Dal"))); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); + services.AddCors(); } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) @@ -42,8 +42,11 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); } - - app.UseHttpsRedirection(); + + app.UseCors(builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .WithExposedHeaders("Authorization", "Accept", "Content-Type", "Origin")); app.UseMiddleware(); app.UseRouting(); diff --git a/Server/Statistics b/Server/Statistics index 4f3b58f..c80bf6a 100644 --- a/Server/Statistics +++ b/Server/Statistics @@ -1 +1 @@ -{"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Wins":0,"Loss":0,"Draws":0,"WinLossRatio":0.0,"TimeSpent":null,"UsedRock":0,"UsedPaper":0,"UsedScissors":0,"Score":0}} \ No newline at end of file +{"1a047fbd-901f-464f-ab6b-e6d0f46f1558":{"Id":"1a047fbd-901f-464f-ab6b-e6d0f46f1558","Login":"string","Wins":0,"Loss":0,"Draws":0,"WinLossRatio":0.0,"TimeSpent":null,"UsedRock":0,"UsedPaper":0,"UsedScissors":0,"Score":0},"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Wins":0,"Loss":0,"Draws":0,"WinLossRatio":0.0,"TimeSpent":null,"UsedRock":0,"UsedPaper":0,"UsedScissors":0,"Score":0}} \ No newline at end of file diff --git a/Server/appsettings.json b/Server/appsettings.json index d9d9a9b..419e7d7 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "connectionString": "Host=localhost;Port=5433;Database=sqlite;Username=sqlite;Password=sqlite" + }, "Logging": { "LogLevel": { "Default": "Information", From f56a7e7c6bb983ac3ae91f671893c3e64e351a77 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 14 May 2021 19:48:09 +0300 Subject: [PATCH 06/56] Some refactoring --- Server.Bll/Models/AccountModel.cs | 11 + Server.Bll/Models/RoomModel.cs | 23 +++ Server.Bll/Models/RoomPlayersModel.cs | 23 +++ Server.Bll/Models/RoundModel.cs | 22 ++ Server.Bll/Models/StatisticsModel.cs | 60 ++++++ Server.Bll/Server.Bll.csproj | 4 - .../{AccountManager.cs => AccountService.cs} | 54 +---- Server.Bll/Services/DeserializedObject.cs | 120 ----------- ...{IAccountManager.cs => IAccountService.cs} | 2 +- .../Interfaces/IDeserializedObject.cs | 19 -- .../{IRoomCoordinator.cs => IRoomService.cs} | 0 ...{IRoundCoordinator.cs => IRoundService.cs} | 0 Server.Bll/Services/Interfaces/IStorage.cs | 90 -------- .../{RoomCoordinator.cs => RoomService.cs} | 8 +- .../{RoundCoordinator.cs => RoundService.cs} | 18 +- Server.Bll/Services/Storage.cs | 193 ------------------ 16 files changed, 161 insertions(+), 486 deletions(-) create mode 100644 Server.Bll/Models/AccountModel.cs create mode 100644 Server.Bll/Models/RoomModel.cs create mode 100644 Server.Bll/Models/RoomPlayersModel.cs create mode 100644 Server.Bll/Models/RoundModel.cs create mode 100644 Server.Bll/Models/StatisticsModel.cs rename Server.Bll/Services/{AccountManager.cs => AccountService.cs} (59%) delete mode 100644 Server.Bll/Services/DeserializedObject.cs rename Server.Bll/Services/Interfaces/{IAccountManager.cs => IAccountService.cs} (97%) delete mode 100644 Server.Bll/Services/Interfaces/IDeserializedObject.cs rename Server.Bll/Services/Interfaces/{IRoomCoordinator.cs => IRoomService.cs} (100%) rename Server.Bll/Services/Interfaces/{IRoundCoordinator.cs => IRoundService.cs} (100%) delete mode 100644 Server.Bll/Services/Interfaces/IStorage.cs rename Server.Bll/Services/{RoomCoordinator.cs => RoomService.cs} (97%) rename Server.Bll/Services/{RoundCoordinator.cs => RoundService.cs} (95%) delete mode 100644 Server.Bll/Services/Storage.cs diff --git a/Server.Bll/Models/AccountModel.cs b/Server.Bll/Models/AccountModel.cs new file mode 100644 index 0000000..1e9a9c9 --- /dev/null +++ b/Server.Bll/Models/AccountModel.cs @@ -0,0 +1,11 @@ + +namespace Server.Dal.Entities +{ + public class Account + { + public string Login { get; set; } + + public string Password { get; set; } + + } +} \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs new file mode 100644 index 0000000..2ad7ddb --- /dev/null +++ b/Server.Bll/Models/RoomModel.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities +{ + public class Room + { + public Round Round { get; set; } + + public RoomPlayers RoomPlayers { get; set; } + + public bool IsPrivate { get; set; } + + public bool IsReady { get; set; } + + public bool IsFull { get; set; } + + public DateTime CreationTime { get; set; } + + public bool IsRoundEnded { get; set; } + + } +} diff --git a/Server.Bll/Models/RoomPlayersModel.cs b/Server.Bll/Models/RoomPlayersModel.cs new file mode 100644 index 0000000..54467da --- /dev/null +++ b/Server.Bll/Models/RoomPlayersModel.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.CodeAnalysis; + +namespace Server.Dal.Entities +{ + [Table("RoomPlayers")] + public class RoomPlayers + { + public Room Room { get; set; } + + public ICollection Accounts { get; set; } + + public int FirstPlayerMove { get; set; } + + public int SecondPlayerMove { get; set; } + + public string RoundId { get; set; } + + public Round Round { get; set; } + } +} \ No newline at end of file diff --git a/Server.Bll/Models/RoundModel.cs b/Server.Bll/Models/RoundModel.cs new file mode 100644 index 0000000..8b15a0d --- /dev/null +++ b/Server.Bll/Models/RoundModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities +{ + [Table("Rounds")] + public class Round + { + public RoomPlayers RoomPlayers { get; set; } + + public bool IsFinished { get; set; } + + public DateTime TimeFinished { get; set; } + + public Account Winner { get; set; } + + public Account Loser { get; set; } + + } +} diff --git a/Server.Bll/Models/StatisticsModel.cs b/Server.Bll/Models/StatisticsModel.cs new file mode 100644 index 0000000..e8c0a99 --- /dev/null +++ b/Server.Bll/Models/StatisticsModel.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities +{ + [Table("Statistics")] + public class Statistics + { + public Account Account { get; set; } + + /// + /// Total amount of Wins + /// + public int? Wins { get; set; } + + /// + /// Total amount of Loses + /// + public int? Loss { get; set; } + + /// + /// Total amount of Draws. OBSOLETE + /// + + public int? Draws { get; set; } + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + public double? WinLossRatio { get; set; } + + /// + /// Ratio for the last 7 days + /// + public string TimeSpent { get; set; } + + /// + /// Times used rock + /// + public int? UsedRock { get; set; } + + /// + /// Times used Paper + /// + public int? UsedPaper { get; set; } + + /// + /// Times used Scissors + /// + public int? UsedScissors { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + public int? Score { get; set; } + + + + } +} \ No newline at end of file diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 9db0afc..cbfa581 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -4,8 +4,4 @@ net5.0 - - - - diff --git a/Server.Bll/Services/AccountManager.cs b/Server.Bll/Services/AccountService.cs similarity index 59% rename from Server.Bll/Services/AccountManager.cs rename to Server.Bll/Services/AccountService.cs index c1c2a15..af7becc 100644 --- a/Server.Bll/Services/AccountManager.cs +++ b/Server.Bll/Services/AccountService.cs @@ -2,45 +2,27 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Server.Contracts; using Server.Exceptions.LogIn; -using Server.Models; using Server.Services.Interfaces; namespace Server.Services { - public class AccountManager : IAccountManager + public class AccountService : IAccountService { - private readonly ILogger _logger; - - + private readonly ConcurrentDictionary _invalidTries = new(); private readonly ConcurrentDictionary _lastTimes = new(); //What am i doing? so stupid private readonly IDeserializedObject _deserializedObject; private const int CoolDownTime = 45; - public ConcurrentDictionary AccountsActive { get; set; } - public AccountManager( - ILogger logger, - IDeserializedObject deserializedObject) + public AccountService() { - _logger = logger; - _deserializedObject = deserializedObject; - AccountsActive = new ConcurrentDictionary(); - } - /// - /// Method to asynchronously sign in - /// - /// account from client - /// Account on server - /// When too many false retries - /// When invalid data - /// When used is already signed in + } + public Task LogInAsync(AccountDto accountDto) { var invalidTryAccount = _invalidTries.FirstOrDefault(x => x.Key == accountDto.Login); @@ -85,12 +67,6 @@ public Task LogInAsync(AccountDto accountDto) return Task.FromResult(sessionId); } - - /// - /// Async method to sign out of account - /// - /// Session id of client - /// bool public async Task LogOutAsync(string sessionId) { var tasks = Task.Factory @@ -98,28 +74,14 @@ public async Task LogOutAsync(string sessionId) return await tasks; } - - /// - /// Checks if this session is active - /// - /// Id of client session - /// bool public async Task IsActive(string sessionId) { - var tasks = Task.Factory.StartNew(() => AccountsActive.ContainsKey(sessionId)); - return await tasks; + throw new NotImplementedException(); } - /// - /// Gets active account from list of active accounts by id of client session - /// - /// Id of client session - /// Account - public Account GetActiveAccountBySessionId(string sessionId) + private Task RemoveOutdatedSession() { - AccountsActive.TryGetValue(sessionId, out var account); - - return account; + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Server.Bll/Services/DeserializedObject.cs b/Server.Bll/Services/DeserializedObject.cs deleted file mode 100644 index 984b2b6..0000000 --- a/Server.Bll/Services/DeserializedObject.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Collections.Concurrent; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.IO; -using Newtonsoft.Json; -using Server.Models; -using Server.Services.Interfaces; - -namespace Server.Services -{ - public class DeserializedObject : IDeserializedObject where T: class - { - private static string _fileName; - - /// - /// Concurrent Dictionary. Heart of our project - /// - public ConcurrentDictionary ConcurrentDictionary { get; set; } - - public DeserializedObject() - { - ConcurrentDictionary = GetData().Result; - } - - /// - /// Method to retrieve data from Dictionary - /// - /// ConcurrentDictionary - private async Task> GetData() - { - return await Deserialize(); - } - - /// - /// Method to update data in ConcurrentDictionary - /// - /// - public async Task UpdateData() - { - await Serialize(); - } - - /// - /// Check if file is available. Returns true if it exists. Else false - /// - /// bool - private Task IsNeededFilesAvailable() - { - return Task.Run(()=> File.Exists(_fileName)); - } - - /// - /// Method to deserialize data from T file - /// - /// ConcurrentDictionary - private async Task> Deserialize() - { - _fileName = typeof(T).Name.Contains("Statistics") ? "Statistics" : typeof(T).Name; - - var exists = IsNeededFilesAvailable().Result; - - FileStream reader; - if (exists && File.ReadAllTextAsync(_fileName).Result != "") //todo*/ - try - { - byte[] fileText; - await using (reader = File.Open(_fileName, FileMode.Open)) - { - fileText = new byte[reader.Length]; - await reader.ReadAsync(fileText, 0, (int)reader.Length); - } - - var decoded = Encoding.ASCII.GetString(fileText); - - - var list = await Task.Run(() => - JsonConvert.DeserializeObject>(decoded)); - return list; - } - catch (FileNotFoundException) - { - File.Create(_fileName); - return new ConcurrentDictionary(); - } - - reader = File.Open(_fileName, FileMode.OpenOrCreate); - reader.Close(); - return new ConcurrentDictionary(); - - } - - /// - /// Method to serialize T objects - /// - /// Void - private async Task Serialize() - { - var streamManager = new RecyclableMemoryStreamManager(); - - using var file = File.Open(_fileName, FileMode.OpenOrCreate); - using var memoryStream = streamManager.GetStream(); - using var writer = new StreamWriter(memoryStream); - - var serializer = JsonSerializer.CreateDefault(); - - serializer.Serialize(writer, ConcurrentDictionary); // FROM STACKOVERFLOW - - await writer.FlushAsync().ConfigureAwait(false); - - memoryStream.Seek(0, SeekOrigin.Begin); - - await memoryStream.CopyToAsync(file).ConfigureAwait(false); - - await file.FlushAsync().ConfigureAwait(false); - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IAccountManager.cs b/Server.Bll/Services/Interfaces/IAccountService.cs similarity index 97% rename from Server.Bll/Services/Interfaces/IAccountManager.cs rename to Server.Bll/Services/Interfaces/IAccountService.cs index f20f482..99b5d7b 100644 --- a/Server.Bll/Services/Interfaces/IAccountManager.cs +++ b/Server.Bll/Services/Interfaces/IAccountService.cs @@ -6,7 +6,7 @@ namespace Server.Services.Interfaces { - public interface IAccountManager + public interface IAccountService { /// /// List of all active account on the server diff --git a/Server.Bll/Services/Interfaces/IDeserializedObject.cs b/Server.Bll/Services/Interfaces/IDeserializedObject.cs deleted file mode 100644 index 46f3b3f..0000000 --- a/Server.Bll/Services/Interfaces/IDeserializedObject.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace Server.Services.Interfaces -{ - public interface IDeserializedObject - { - /// - /// Concurrent Dictionary. Heart of our project - /// - ConcurrentDictionary ConcurrentDictionary { get; set; } - - /// - /// Method to update data in ConcurrentDictionary - /// - /// - Task UpdateData(); - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomCoordinator.cs b/Server.Bll/Services/Interfaces/IRoomService.cs similarity index 100% rename from Server.Bll/Services/Interfaces/IRoomCoordinator.cs rename to Server.Bll/Services/Interfaces/IRoomService.cs diff --git a/Server.Bll/Services/Interfaces/IRoundCoordinator.cs b/Server.Bll/Services/Interfaces/IRoundService.cs similarity index 100% rename from Server.Bll/Services/Interfaces/IRoundCoordinator.cs rename to Server.Bll/Services/Interfaces/IRoundService.cs diff --git a/Server.Bll/Services/Interfaces/IStorage.cs b/Server.Bll/Services/Interfaces/IStorage.cs deleted file mode 100644 index 2fd1d94..0000000 --- a/Server.Bll/Services/Interfaces/IStorage.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Server.Exceptions.Register; - -namespace Server.Services.Interfaces -{ - public interface IStorage where T: class - { - /// - /// Gets all elements from T collection - /// - /// ICollection - ICollection GetAll(); - - /// - /// Gets asynchronously all elements from T collection - /// - /// Task of ICollection - Task> GetAllAsync(); - - /// - /// Gets element by Id - /// - /// Id of an element - /// T item - T Get(string id); - - /// - /// Gets asynchronously element by Id - /// - /// string If of an Element - /// Task T item - Task GetAsync(string id); - - /// - /// Adds T element to the collection - /// - /// T item - /// int - /// This account is already exists - /// Generic for custom user exceptions - int Add(T item); - - /// - /// Adds asynchronously an item to ConcurrentDictionary - /// - /// T item - /// int - Task AddAsync(T item); - - /// - /// Adds or updates item in collection - /// - /// id of item - /// T item - void AddOrUpdate(string id, T item); - - /// - /// Asynchronously adds or updates item in collection - /// - /// id of item - /// T item - Task AddOrUpdateAsync(string id, T item); - - /// - /// Updates value in collection - /// - /// - /// - /// Task - Task UpdateAsync(string id, T item); - - /// - /// Deletes item by Id - /// - /// - /// bool - bool Delete(string id); - - /// - /// Asynchronously deletes item by id - /// - /// - /// bool - /// - Task DeleteAsync(string id); - - } -} \ No newline at end of file diff --git a/Server.Bll/Services/RoomCoordinator.cs b/Server.Bll/Services/RoomService.cs similarity index 97% rename from Server.Bll/Services/RoomCoordinator.cs rename to Server.Bll/Services/RoomService.cs index 6af6d41..5486ad3 100644 --- a/Server.Bll/Services/RoomCoordinator.cs +++ b/Server.Bll/Services/RoomService.cs @@ -14,7 +14,7 @@ namespace Server.GameLogic.LogicServices { public class RoomCoordinator : IRoomCoordinator { - private readonly IAccountManager _accountManager; + private readonly IAccountService _accountService; private readonly IRoundCoordinator _roundCoordinator; @@ -25,10 +25,10 @@ public class RoomCoordinator : IRoomCoordinator public ConcurrentDictionary ActiveRooms { get; } public RoomCoordinator( - IAccountManager accountManager, + IAccountService accountService, IRoundCoordinator roundCoordinator) { - _accountManager = accountManager; + _accountService = accountService; _roundCoordinator = roundCoordinator; ActiveRooms = new ConcurrentDictionary(); _timer = new Timer(CheckRoomDate, null, 0, 10000); @@ -277,7 +277,7 @@ private static string RandomString() } private Account GetAccountBySessionId(string sessionId) { - _accountManager.AccountsActive.TryGetValue(sessionId, out var account); + _accountService.AccountsActive.TryGetValue(sessionId, out var account); if (account != null) return account; throw new UserNotFoundException(nameof(account)); diff --git a/Server.Bll/Services/RoundCoordinator.cs b/Server.Bll/Services/RoundService.cs similarity index 95% rename from Server.Bll/Services/RoundCoordinator.cs rename to Server.Bll/Services/RoundService.cs index f9ee69b..0903f76 100644 --- a/Server.Bll/Services/RoundCoordinator.cs +++ b/Server.Bll/Services/RoundService.cs @@ -16,17 +16,17 @@ public class RoundCoordinator : IRoundCoordinator private readonly IStorage _storageStatistics; - private readonly IAccountManager _accountManager; + private readonly IAccountService _accountService; public ConcurrentDictionary ActiveRounds { get; set; } public RoundCoordinator( IStorage storageRounds, - IAccountManager accountManager, + IAccountService accountService, IStorage storageStatistics) { _storageRounds = storageRounds; - _accountManager = accountManager; + _accountService = accountService; _storageStatistics = storageStatistics; ActiveRounds = new ConcurrentDictionary(); } @@ -36,7 +36,7 @@ public async Task MakeMove(string roomId, string sessionId, int move) { var tasks = Task.Factory.StartNew(async () => { - var accountId = _accountManager.GetActiveAccountBySessionId(sessionId).Id; + var accountId = _accountService.GetActiveAccountBySessionId(sessionId).Id; ActiveRounds.TryGetValue(roomId, out var thisRound); if (thisRound == null) @@ -108,11 +108,11 @@ public async Task MakeMove(string roomId, string sessionId, int move) if (winner == "Bot") { thisRound.WinnerId = winner; - thisRound.LoserId = _accountManager.AccountsActive.FirstOrDefault(x=> x.Value.Id==thisPlayerKey).Value.Login; + thisRound.LoserId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==thisPlayerKey).Value.Login; } else { - thisRound.WinnerId = _accountManager.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; + thisRound.WinnerId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; thisRound.LoserId = "Bot"; } } @@ -132,8 +132,8 @@ public async Task MakeMove(string roomId, string sessionId, int move) else { thisRound.IsFinished = true; - thisRound.WinnerId = _accountManager.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; - thisRound.LoserId = _accountManager.AccountsActive.FirstOrDefault(x=> x.Value.Id==loserId).Value.Login; + thisRound.WinnerId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; + thisRound.LoserId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==loserId).Value.Login; thisRound.TimeFinished = DateTime.Now; } await _storageRounds.AddAsync(thisRound); @@ -158,7 +158,7 @@ private async Task FillStatistics(IRound thisRound) var keys = thisRound.PlayerMoves.Keys; foreach (var key in keys) //FIX { - var thisAccountLogin = _accountManager.AccountsActive.FirstOrDefault(x => x.Value.Id == key); + var thisAccountLogin = _accountService.AccountsActive.FirstOrDefault(x => x.Value.Id == key); var statistics = await _storageStatistics.GetAsync(key); //here is the problem. if (thisRound.WinnerId.Equals(thisAccountLogin.Value.Login)) diff --git a/Server.Bll/Services/Storage.cs b/Server.Bll/Services/Storage.cs deleted file mode 100644 index 733b5d7..0000000 --- a/Server.Bll/Services/Storage.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Server.Exceptions.Register; -using Server.Services.Interfaces; - -namespace Server.Services -{ - public class Storage : IStorage where T: class - { - private readonly IDeserializedObject _deserializedObject; //todo: change into something else. - - public Storage( - IDeserializedObject deserializedObject) - { - _deserializedObject = deserializedObject; - } - - /// - /// Gets all elements from T collection - /// - /// ICollection - - public ICollection GetAll() - { - return _deserializedObject.ConcurrentDictionary.Values; - } - - /// - /// Gets asynchronously all elements from T collection - /// - /// Task of ICollection - public async Task> GetAllAsync() - { - var result = Task.Run(GetAll); - return await result; - } - - /// - /// Gets element by Id - /// - /// Id of an element - /// T item - public T Get(string id) - { - return _deserializedObject.ConcurrentDictionary.TryGetValue(id, out var account) ? account : default; - } - - /// - /// Gets asynchronously element by Id - /// - /// string If of an Element - /// Task T item - public async Task GetAsync(string id) - { - var task = Task.Run(() => Get(id)); - return await task; - } - - /// - /// Adds T element to the collection - /// - /// T item - /// int - /// - /// - public int Add(T item) - { - var guid = GetGuidFromT(item); - if (typeof(T).Name.Contains("Round")) - { - if (!_deserializedObject.ConcurrentDictionary.TryAdd(guid.ToString(), item)) throw new UnknownReasonException(item.GetType().ToString()); - _deserializedObject.UpdateData(); - return (int)HttpStatusCode.OK; - } - - if (CheckIfExists(item)) - throw new AlreadyExistsException(item.GetType().ToString()); - - if (!_deserializedObject.ConcurrentDictionary.TryAdd(guid.ToString(), item)) throw new UnknownReasonException(item.GetType().ToString()); - _deserializedObject.UpdateData(); - return (int)HttpStatusCode.OK; - } - - /// - /// Adds asynchronously an item to ConcurrentDictionary - /// - /// T item - /// int - public Task AddAsync(T item) - { - return Task.Factory.StartNew(() => Add(item)); - } - - /// - /// Adds or updates item in collection - /// - /// id of item - /// T item - public void AddOrUpdate(string id, T item) - { - _deserializedObject.ConcurrentDictionary[id] = item; - } - /// - /// Asynchronously adds or updates item in collection - /// - /// id of item - /// T item - public Task AddOrUpdateAsync(string id, T item) - { - throw new NotImplementedException(); - } - - /// - /// Updates value in collection - /// - /// - /// - /// Task - public Task UpdateAsync(string id, T item) - { - return Task.Factory.StartNew(() => - { - var thisItem = _deserializedObject.ConcurrentDictionary[id]; - _deserializedObject.ConcurrentDictionary.TryUpdate(id, item, thisItem); - _deserializedObject.UpdateData(); - }); - } - - /// - /// Deletes item by Id - /// - /// - /// bool - public bool Delete(string id) - { - return _deserializedObject.ConcurrentDictionary.TryRemove(id, out _); - } - - /// - /// Asynchronously deletes item by id - /// - /// - /// bool - /// - public Task DeleteAsync(string id) - { - throw new NotImplementedException(); - } - - #region PrivateMethods - - /// - /// Gets Guid from T item - /// - /// - /// object string - private object GetGuidFromT(T item) - { - //METHOD TO GET GUID FROM T - //*************************** - var t = item.GetType(); - var prop = t.GetProperty("Id"); - return prop?.GetValue(item); - // ************************************* - } - - /// - /// Gets login property from T item - /// - /// - /// object string - private object GetLoginString(T item) - { - return item.GetType().GetProperty("Login") != null ? item.GetType().GetProperty("Login")?.GetValue(item) : null; - } - - /// - /// Checks if T item exists in collection - /// - /// - /// bool - private bool CheckIfExists(T item) - { - var flattenList = _deserializedObject.ConcurrentDictionary.Values; //THIS IS NOT ASYNC - return GetLoginString(item) != null && flattenList.Any(T => GetLoginString(T).Equals(GetLoginString(item))); - } - - #endregion - } -} \ No newline at end of file From d5c556ed08c1bbd39f7774f20b070dd82c04633e Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 14 May 2021 20:23:19 +0300 Subject: [PATCH 07/56] Some more refactoring --- Server.Bll/Models/AccountModel.cs | 8 +- Server.Bll/Models/RoomModel.cs | 26 +- Server.Bll/Models/RoomPlayersModel.cs | 24 +- Server.Bll/Models/RoundModel.cs | 23 +- Server.Bll/Models/StatisticsModel.cs | 51 ++- Server.Bll/Services/AccountService.cs | 87 ----- .../Services/Interfaces/IAccountService.cs | 44 +-- .../Services/Interfaces/IRoomService.cs | 75 +--- .../Services/Interfaces/IRoundService.cs | 38 +-- Server.Bll/Services/RockPaperScissors.cs | 85 +---- Server.Bll/Services/RoomService.cs | 296 ---------------- Server.Bll/Services/RoundService.cs | 323 ------------------ Server/Extensions/DatabaseExtension.cs | 18 + Server/Extensions/LoggingMiddleware.cs | 28 +- Server/Extensions/ServiceExtension.cs | 15 +- Server/Extensions/SwaggerExtension.cs | 18 + Server/Startup.cs | 12 +- 17 files changed, 125 insertions(+), 1046 deletions(-) delete mode 100644 Server.Bll/Services/AccountService.cs delete mode 100644 Server.Bll/Services/RoomService.cs delete mode 100644 Server.Bll/Services/RoundService.cs create mode 100644 Server/Extensions/DatabaseExtension.cs create mode 100644 Server/Extensions/SwaggerExtension.cs diff --git a/Server.Bll/Models/AccountModel.cs b/Server.Bll/Models/AccountModel.cs index 1e9a9c9..533348d 100644 --- a/Server.Bll/Models/AccountModel.cs +++ b/Server.Bll/Models/AccountModel.cs @@ -1,11 +1,9 @@ - -namespace Server.Dal.Entities +namespace Server.Bll.Models { - public class Account + public class AccountModel { public string Login { get; set; } - + public string Password { get; set; } - } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs index 2ad7ddb..9c22c62 100644 --- a/Server.Bll/Models/RoomModel.cs +++ b/Server.Bll/Models/RoomModel.cs @@ -1,23 +1,21 @@ using System; -using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Bll.Models { - public class Room + public class RoomModel { - public Round Round { get; set; } + public RoundModel Round { get; set; } + + public RoomPlayersModel RoomPlayers { get; set; } - public RoomPlayers RoomPlayers { get; set; } - public bool IsPrivate { get; set; } - - public bool IsReady { get; set; } - + + public bool IsReady { get; set; } + public bool IsFull { get; set; } - - public DateTime CreationTime { get; set; } - + + public DateTime CreationTime { get; set; } + public bool IsRoundEnded { get; set; } - } -} +} \ No newline at end of file diff --git a/Server.Bll/Models/RoomPlayersModel.cs b/Server.Bll/Models/RoomPlayersModel.cs index 54467da..74efb40 100644 --- a/Server.Bll/Models/RoomPlayersModel.cs +++ b/Server.Bll/Models/RoomPlayersModel.cs @@ -1,23 +1,17 @@ using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Diagnostics.CodeAnalysis; -namespace Server.Dal.Entities +namespace Server.Bll.Models { - [Table("RoomPlayers")] - public class RoomPlayers + public class RoomPlayersModel { - public Room Room { get; set; } - - public ICollection Accounts { get; set; } - - public int FirstPlayerMove { get; set; } - - public int SecondPlayerMove { get; set; } + public RoomModel Room { get; set; } - public string RoundId { get; set; } + public ICollection Accounts { get; set; } - public Round Round { get; set; } + public int FirstPlayerMove { get; set; } + + public int SecondPlayerMove { get; set; } + + public RoundModel Round { get; set; } } } \ No newline at end of file diff --git a/Server.Bll/Models/RoundModel.cs b/Server.Bll/Models/RoundModel.cs index 8b15a0d..5d0af48 100644 --- a/Server.Bll/Models/RoundModel.cs +++ b/Server.Bll/Models/RoundModel.cs @@ -1,22 +1,17 @@ using System; -using System.Collections.Concurrent; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Bll.Models { - [Table("Rounds")] - public class Round + public class RoundModel { - public RoomPlayers RoomPlayers { get; set; } - + public RoomPlayersModel RoomPlayers { get; set; } + public bool IsFinished { get; set; } - + public DateTime TimeFinished { get; set; } - - public Account Winner { get; set; } - public Account Loser { get; set; } - + public AccountModel Winner { get; set; } + + public AccountModel Loser { get; set; } } -} +} \ No newline at end of file diff --git a/Server.Bll/Models/StatisticsModel.cs b/Server.Bll/Models/StatisticsModel.cs index e8c0a99..6014b52 100644 --- a/Server.Bll/Models/StatisticsModel.cs +++ b/Server.Bll/Models/StatisticsModel.cs @@ -1,60 +1,53 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Server.Dal.Entities +namespace Server.Bll.Models { - [Table("Statistics")] - public class Statistics + public class StatisticsModel { - public Account Account { get; set; } - + public AccountModel Account { get; set; } + /// - /// Total amount of Wins + /// Total amount of Wins /// public int? Wins { get; set; } - + /// - /// Total amount of Loses + /// Total amount of Loses /// public int? Loss { get; set; } - + /// - /// Total amount of Draws. OBSOLETE + /// Total amount of Draws. OBSOLETE /// - + public int? Draws { get; set; } - + /// - /// Ratio Wins to Losses. Win/Loss * 100 + /// Ratio Wins to Losses. Win/Loss * 100 /// public double? WinLossRatio { get; set; } - + /// - /// Ratio for the last 7 days + /// Ratio for the last 7 days /// public string TimeSpent { get; set; } - + /// - /// Times used rock + /// Times used rock /// public int? UsedRock { get; set; } - + /// - /// Times used Paper + /// Times used Paper /// public int? UsedPaper { get; set; } - + /// - /// Times used Scissors + /// Times used Scissors /// public int? UsedScissors { get; set; } - + /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// public int? Score { get; set; } - - - } } \ No newline at end of file diff --git a/Server.Bll/Services/AccountService.cs b/Server.Bll/Services/AccountService.cs deleted file mode 100644 index af7becc..0000000 --- a/Server.Bll/Services/AccountService.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; -using Server.Exceptions.LogIn; -using Server.Services.Interfaces; - -namespace Server.Services -{ - public class AccountService : IAccountService - { - - private readonly ConcurrentDictionary _invalidTries = new(); - private readonly ConcurrentDictionary _lastTimes = new(); //What am i doing? so stupid - - private readonly IDeserializedObject _deserializedObject; - private const int CoolDownTime = 45; - public ConcurrentDictionary AccountsActive { get; set; } - - - public AccountService() - { - - } - - public Task LogInAsync(AccountDto accountDto) - { - var invalidTryAccount = _invalidTries.FirstOrDefault(x => x.Key == accountDto.Login); - - if (invalidTryAccount.Value >= 2) - { - if ((DateTime.Now - _lastTimes - .FirstOrDefault(x => x.Key == accountDto.Login).Value) - .TotalSeconds >= CoolDownTime) - { - _invalidTries.TryRemove(invalidTryAccount); - } - else - { - _lastTimes.AddOrUpdate(accountDto.Login, accountDto.LastRequest, - ((s, time) => time = accountDto.LastRequest)); - throw new LoginCooldownException("CoolDown", CoolDownTime); - } - } - - var login = _deserializedObject.ConcurrentDictionary.Values - .FirstOrDefault(x => x.Login == accountDto.Login && x.Password == accountDto.Password); - - if (login == null) - { - _invalidTries.AddOrUpdate(accountDto.Login, 1, (s, i) => i + 1); - - _lastTimes.AddOrUpdate(accountDto.Login, accountDto.LastRequest, - ((s, time) => time = accountDto.LastRequest)); - - throw new InvalidCredentialsException($"{accountDto.Login}"); - } - - if (AccountsActive.Any(x => x.Value == login)) - { - throw new UserAlreadySignedInException(nameof(login.Login)); - } - - var sessionId = Guid.NewGuid().ToString(); - AccountsActive.TryAdd(sessionId, login); - _logger.LogTrace($"{sessionId} to {login}"); //todo - - return Task.FromResult(sessionId); - } - public async Task LogOutAsync(string sessionId) - { - var tasks = Task.Factory - .StartNew(() => AccountsActive.ContainsKey(sessionId) && AccountsActive.TryRemove(sessionId, out _)); - - return await tasks; - } - public async Task IsActive(string sessionId) - { - throw new NotImplementedException(); - } - - private Task RemoveOutdatedSession() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IAccountService.cs b/Server.Bll/Services/Interfaces/IAccountService.cs index 99b5d7b..c638c57 100644 --- a/Server.Bll/Services/Interfaces/IAccountService.cs +++ b/Server.Bll/Services/Interfaces/IAccountService.cs @@ -1,47 +1,11 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Server.Contracts; -using Server.Exceptions.LogIn; -using Server.Models; +using System.Threading.Tasks; namespace Server.Services.Interfaces { public interface IAccountService { - /// - /// List of all active account on the server - /// - ConcurrentDictionary AccountsActive { get; set; } - - /// - /// Method to asynchronously sign in - /// - /// account from client - /// Account on server - /// When too many false retries - /// When invalid data - /// When used is already signed in - Task LogInAsync(AccountDto accountDto); - - /// - /// Async method to sign out of account - /// - /// Session id of client - /// bool - Task LogOutAsync(string sessionId); - - /// - /// Checks if this session is active - /// - /// Id of client session - /// bool - Task IsActive(string sessionId); - - /// - /// Gets active account from list of active accounts by id of client session - /// - /// Id of client session - /// Account - Account GetActiveAccountBySessionId(string sessionId); + Task RegisterAsync(); + Task LogInAsync(); + Task LogOutAsync(); } } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 3f04569..6ce60ab 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,74 +1,13 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Server.GameLogic.Models; +using System.Threading.Tasks; +using Server.Bll.Models; -namespace Server.GameLogic.LogicServices.Interfaces +namespace Server.Bll.Services.Interfaces { public interface IRoomCoordinator { - - /// - /// List of active rooms. Made for monitoring - /// - ConcurrentDictionary ActiveRooms { get; } - - /// - /// Method to create a room - /// - /// - /// - /// - Task CreateRoom(string sessionId, bool isPrivate); - - /// - /// Method to join a private room by id - /// - /// - /// - /// - Task JoinPrivateRoom(string sessionId, string roomId); - - /// - /// Method to join random public room - /// - /// - /// - Task JoinPublicRoom(string sessionId); - - /// - /// Method to play with Bot - /// - /// - /// - Task CreateTrainingRoom(string sessionId); - - /// - /// OBSOLETE method to update Rooms - /// - /// - /// - Task UpdateRoom(Room updated); - - /// - /// Method to update Rooms - /// - /// - /// - Task UpdateRoom(string roomId); - - /// - /// Updates player status - /// - /// - /// - /// - Task UpdatePlayerStatus(string sessionId, bool isReady); - - /// - /// Deletes room - /// - /// - /// - Task DeleteRoom(string roomId); + Task CreateRoom(); + Task JoinRoom(); + Task UpdateRoom(); + Task DeleteRoom(); } } diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index c7f1f19..850455a 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -1,37 +1,13 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Server.GameLogic.Models; +using System.Threading.Tasks; +using Server.Bll.Models; -namespace Server.GameLogic.LogicServices.Interfaces +namespace Server.Bll.Services.Interfaces { public interface IRoundCoordinator { - /// - /// Task to make a move - /// - /// current room Id - /// Current account Id - /// Move that he has done - /// Round - Task MakeMove(string roomId, string accountId, int move); - - /// - /// Dictionary of active rounds. Made for monitoring - /// - ConcurrentDictionary ActiveRounds { get; set; } - - /// - /// Gets current active round for special room - /// - /// id of this round - /// Round - Task GetCurrentActiveRoundForSpecialRoom(string roundId); - - /// - /// Updates rounds - /// - /// id of this room - /// Round - Task UpdateRound(string roomId); + Task CreateRoundAsync(); + Task MakeMoveAsync(); + Task UpdateRoundAsync(); + Task UpdateRound(); } } \ No newline at end of file diff --git a/Server.Bll/Services/RockPaperScissors.cs b/Server.Bll/Services/RockPaperScissors.cs index acd94ce..b406454 100644 --- a/Server.Bll/Services/RockPaperScissors.cs +++ b/Server.Bll/Services/RockPaperScissors.cs @@ -1,91 +1,8 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using Server.GameLogic.Models; - -namespace Server.GameLogic +namespace Server.Bll.Services { public static class RockPaperScissors { - public static ConcurrentDictionary UpdateMove( - ConcurrentDictionary playerMoves, string accountId, int move) - { - playerMoves.TryUpdate(accountId, (RequiredGameMove)move, - playerMoves.FirstOrDefault(x=> x.Key.Equals(accountId)).Value); - - return playerMoves; - } - /// - /// Implementation or Rock Paper Scissors Game. - /// - /// ConcurrentDictionary of players - /// string Winner - public static string MoveComparator( - ConcurrentDictionary playerMoves) - { - var surrenderMove = 0; - var winner = string.Empty; - - foreach (var (key, value) in playerMoves) - { - - if (surrenderMove == 0 && string.IsNullOrEmpty(winner)) - { - winner = key; - surrenderMove = (int)value; - } - else - { - if ((int)value == surrenderMove) - { - return string.Empty; - } - - if (surrenderMove == 1 && (int)value == 3) - continue; - if (surrenderMove == 2 && (int)value == 1) - continue; - else if (surrenderMove == 3 && (int)value == 2) - continue; - else if (surrenderMove == 1 && (int)value == 2) - winner = key; - else if (surrenderMove == 2 && (int)value == 3) - winner = key; - else if (surrenderMove == 3 && (int)value == 1) - winner = key; - } - } - return winner; - } - - /// - /// Changes Bot move if he is in room - /// - /// ConcurrentDictionary of players - /// ConcurrentDictionary of players - public static ConcurrentDictionary ChangeBotState( - ConcurrentDictionary playerMoves) - { - var (key, value) = playerMoves.FirstOrDefault(x => x.Key.Equals("Bot")); - playerMoves.TryUpdate(key, GenerateRandomMove(), value); - - return playerMoves; - } - - /// - /// Gets randomized move for the bot - /// - /// Enumerator RequiredGameMove - private static RequiredGameMove GenerateRandomMove() - { - var r = new Random(); - var rInt = r.Next(1, 4); - - return (RequiredGameMove)rInt; - } } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs deleted file mode 100644 index 5486ad3..0000000 --- a/Server.Bll/Services/RoomService.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Server.Exceptions.LogIn; -using Server.GameLogic.Exceptions; -using Server.GameLogic.LogicServices.Interfaces; -using Server.GameLogic.Models; -using Server.Models; -using Server.Services.Interfaces; - -namespace Server.GameLogic.LogicServices -{ - public class RoomCoordinator : IRoomCoordinator - { - private readonly IAccountService _accountService; - - private readonly IRoundCoordinator _roundCoordinator; - - private static readonly Random Random = new(); - - private Timer _timer; - - public ConcurrentDictionary ActiveRooms { get; } - - public RoomCoordinator( - IAccountService accountService, - IRoundCoordinator roundCoordinator) - { - _accountService = accountService; - _roundCoordinator = roundCoordinator; - ActiveRooms = new ConcurrentDictionary(); - _timer = new Timer(CheckRoomDate, null, 0, 10000); - } - - public async Task CreateRoom(string sessionId, bool isPrivate) - { - var tasks = Task.Factory.StartNew(() => - { - var account = GetAccountBySessionId(sessionId); - - if (ActiveRooms.Any(x => x.Value.Players.Any(p => p.Key.Equals(account.Id)))) - throw new TwinkGameRoomCreationException(); - - var newRoom = new Room - { - RoomId = RandomString(), - Players = new ConcurrentDictionary(), - IsPrivate = isPrivate, - IsReady = false, - IsRoundEnded = false, - IsFull = false, - CreationTime = DateTime.Now - }; - - if (newRoom.Players.TryAdd(account.Id, false)) - { - ActiveRooms.TryAdd(newRoom.RoomId, newRoom); - } - - //_timer = new Timer(tm, null, 0, 10000); //todo: implement - return newRoom; - }); - return await tasks; - } - - public async Task JoinPublicRoom(string sessionId) - { - var tasks = Task.Factory.StartNew(() => - { - var thisRoom = ActiveRooms - .FirstOrDefault(x => - x.Value.IsPrivate == false - && x.Value.Players.Count < 2) - .Value; - - var thisAccount = GetAccountBySessionId(sessionId); - - thisRoom.Players.TryAdd(thisAccount.Id, false); - - return UpdateRoom(thisRoom).Result; - }); - return await tasks; - } - - public async Task CreateTrainingRoom(string sessionId) - { - var tasks = Task.Factory.StartNew(() => - { - var account = GetAccountBySessionId(sessionId); - - if (ActiveRooms.Any(x => x.Value.Players.Any(p => p.Key.Equals(account.Id)))) - throw new TwinkGameRoomCreationException(); - - var newRoom = new Room - { - RoomId = RandomString(), - Players = new ConcurrentDictionary(), - IsPrivate = true, - IsReady = false, - IsRoundEnded = false, - IsFull = false, - CreationTime = DateTime.Now - }; - - newRoom.Players.TryAdd(account.Id, false); - newRoom.Players.TryAdd("Bot", true); - newRoom.IsFull = true; - ActiveRooms.TryAdd(newRoom.RoomId, newRoom); - - return newRoom; - }); - return await tasks; - } - - public async Task JoinPrivateRoom(string sessionId, string roomId) - { - var tasks = Task.Run(() => - { - if (!ActiveRooms.TryGetValue(roomId, out var thisRoom)) - return null; //todo:exception; - - if (thisRoom.Players.Count == 2) - return null; - - var newRoom = thisRoom; - var thisAccount = GetAccountBySessionId(sessionId); - newRoom.Players.TryAdd(thisAccount.Id, false); - - if (newRoom.Players.Count > 1) - newRoom.IsFull = true; - - return ActiveRooms.TryUpdate(roomId, - newRoom, thisRoom) - ? newRoom - : null; //todo: change to exception; - }); - - return await tasks; - } - - private async void CheckRoomDate(object state) - { - var threads = Task.Factory.StartNew(() => - { - if (ActiveRooms.IsEmpty) return; - foreach (var room in ActiveRooms) - { - if (room.Value.CreationTime.AddMinutes(5) < DateTime.Now && room.Value.CurrentRoundId == null) - ActiveRooms.TryRemove(room); - } - }); - await Task.WhenAll(threads); - } - - public async Task DeleteRoom(string roomId) - { - var tasks = Task.Factory.StartNew(() => - ActiveRooms.TryRemove(roomId, out _)); - return await tasks; - } - - public async Task UpdateRoom(Room updated) - { - var thread = Task.Factory.StartNew(() => - { - ActiveRooms.TryGetValue(updated.RoomId, out var room); - if (room == null) - { - return null; //todo: change into exception; - } - - return ActiveRooms.TryUpdate(room.RoomId, - updated, room) - ? room - : null; - }); - return await thread; - } - public async Task UpdatePlayerStatus(string sessionId, bool isReady) - { - var account = GetAccountBySessionId(sessionId); - - var room = ActiveRooms.Values - .FirstOrDefault(x => x.Players.Keys - .Any(p => p - .Equals(account.Id))); - - var thisRoom = GetRoomByRoomId(room?.RoomId); - - if (thisRoom == null) - return null; //Never performs - - - var (key, oldValue) = - thisRoom.Players.FirstOrDefault(x => x.Key == account.Id); - - - thisRoom.Players.TryUpdate(key, isReady, oldValue); - - if (thisRoom.Players.Values.All(x => x) && thisRoom.Players.Count == 2) - { - thisRoom.IsReady = true; - - thisRoom.IsFull = true; - - if (thisRoom.CurrentRoundId != null) - return thisRoom; - - var round = new Round - { - Id = Guid.NewGuid() - .ToString(), - IsFinished = false, - PlayerMoves = new ConcurrentDictionary(), - TimeFinished = DateTime.Now, - WinnerId = null, - LoserId = null, - }; - - foreach (var value in thisRoom.Players.Keys.ToList()) - { - round.PlayerMoves.TryAdd(value, RequiredGameMove.Default); - } - - thisRoom.CurrentRoundId = round.Id; - - _roundCoordinator.ActiveRounds.TryAdd(thisRoom.RoomId, round); - - } - - return await UpdateRoom(thisRoom); - } - public async Task UpdateRoom(string roomId) - { - try - { - var room = GetRoomByRoomId(roomId); - - var thisRound = _roundCoordinator.ActiveRounds.FirstOrDefault(x => x.Key.Equals(room.RoomId)); - - if (thisRound.Value != null && thisRound.Value.IsFinished) - { - room.IsReady = false; - room.IsRoundEnded = false; - room.CurrentRoundId = null; - foreach (var (key, value) in room.Players) - { - if (key.Equals("Bot")) - room.Players.TryUpdate(key, true, value); - else - { - room.Players.TryUpdate(key, false, value); - } - } - _roundCoordinator.ActiveRounds.TryRemove(thisRound); - - await UpdateRoom(room); - } - - return await UpdateRoom(room); - } - catch (Exception) - { - return null; - } - - } - - #region PrivateMethods - private static string RandomString() - { - const string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; - return new string(Enumerable.Repeat(chars, 5) - .Select(s => s[Random.Next(s.Length)]).ToArray()); - } - private Account GetAccountBySessionId(string sessionId) - { - _accountService.AccountsActive.TryGetValue(sessionId, out var account); - if (account != null) - return account; - throw new UserNotFoundException(nameof(account)); - - } - private Room GetRoomByRoomId(string roomId) - { - return ActiveRooms.TryGetValue(roomId, out var thisRoom) - ? thisRoom - : throw new UserNotFoundException(); - } - - #endregion - - } -} \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs deleted file mode 100644 index 0903f76..0000000 --- a/Server.Bll/Services/RoundService.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; -using Server.GameLogic.LogicServices.Interfaces; -using Server.GameLogic.Models; -using Server.GameLogic.Models.Interfaces; -using Server.Models; -using Server.Services.Interfaces; - -namespace Server.GameLogic.LogicServices -{ - public class RoundCoordinator : IRoundCoordinator - { - private readonly IStorage _storageRounds; - - private readonly IStorage _storageStatistics; - - private readonly IAccountService _accountService; - - public ConcurrentDictionary ActiveRounds { get; set; } - - public RoundCoordinator( - IStorage storageRounds, - IAccountService accountService, - IStorage storageStatistics) - { - _storageRounds = storageRounds; - _accountService = accountService; - _storageStatistics = storageStatistics; - ActiveRounds = new ConcurrentDictionary(); - } - - - public async Task MakeMove(string roomId, string sessionId, int move) - { - var tasks = Task.Factory.StartNew(async () => - { - var accountId = _accountService.GetActiveAccountBySessionId(sessionId).Id; - ActiveRounds.TryGetValue(roomId, out var thisRound); - - if (thisRound == null) - return null; //todo: exception; - - if (thisRound.IsFinished) - { - return thisRound; - } - - //************************************************************************************************************************************ - var elapsedTime = DateTime.Now.Subtract(thisRound.TimeFinished); - if (elapsedTime.Seconds>= 200 && - thisRound.PlayerMoves.Any(x => x.Value.Equals(RequiredGameMove.Default))) - { - var dictionary = thisRound.PlayerMoves; - var first = dictionary.Keys.First(); - var last = dictionary.Keys.Last(); - - if (dictionary[first] == dictionary[last]) - thisRound.IsDraw = false; - else if (dictionary[first] == RequiredGameMove.Default) - { - thisRound.LoserId = first; - thisRound.WinnerId = last; - } - else - { - thisRound.LoserId = last; - thisRound.WinnerId = first; - } - thisRound.TimeFinished = DateTime.Now; - thisRound.IsFinished = true; - - await UpdateRound(thisRound); - return thisRound; - } - - thisRound.TimeFinished = DateTime.Now; - - //************************************************************************************************************************ - - var botPlays = false; - if (thisRound.PlayerMoves.Any(x => x.Key.Equals("Bot"))) - { - thisRound.PlayerMoves = RockPaperScissors.ChangeBotState(thisRound.PlayerMoves); - botPlays = true; - } - thisRound.PlayerMoves = RockPaperScissors.UpdateMove(thisRound.PlayerMoves, accountId, move); - - if (thisRound.PlayerMoves.Values.All(x => x != RequiredGameMove.Default)) - { - var winner = RockPaperScissors.MoveComparator(thisRound.PlayerMoves); - - if (string.IsNullOrEmpty(winner)) - { - thisRound.IsDraw = false; - thisRound.WinnerId = "DRAW"; - thisRound.LoserId = "DRAW"; - await UpdateRound(thisRound); - } - - if (botPlays) - { - thisRound.IsFinished = true; - var thisPlayerKey = - thisRound.LoserId = thisRound.PlayerMoves - .FirstOrDefault(x => x.Key != "Bot").Key; - if (winner == "Bot") - { - thisRound.WinnerId = winner; - thisRound.LoserId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==thisPlayerKey).Value.Login; - } - else - { - thisRound.WinnerId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; - thisRound.LoserId = "Bot"; - } - } - - else - { - var loserId = thisRound.PlayerMoves.FirstOrDefault(x => x.Key != winner).Key; - - //////////////////////////////////////////////////////////////////////////// - if (thisRound.WinnerId == "DRAW" || thisRound.LoserId == "DRAW") - { - thisRound.IsFinished = true; - thisRound.WinnerId = "DRAW"; - thisRound.LoserId = "DRAW"; - thisRound.TimeFinished = DateTime.Now; - } - else - { - thisRound.IsFinished = true; - thisRound.WinnerId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==winner).Value.Login; - thisRound.LoserId = _accountService.AccountsActive.FirstOrDefault(x=> x.Value.Id==loserId).Value.Login; - thisRound.TimeFinished = DateTime.Now; - } - await _storageRounds.AddAsync(thisRound); - await FillStatistics(thisRound); - } - - } - - await UpdateRound(thisRound); - - return thisRound; - - }); - return await await tasks; //AWAIT AWAIT? - - } - - private async Task FillStatistics(IRound thisRound) - { - if (thisRound.WinnerId == "DRAW" || thisRound.LoserId == "DRAW") - return; - var keys = thisRound.PlayerMoves.Keys; - foreach (var key in keys) //FIX - { - var thisAccountLogin = _accountService.AccountsActive.FirstOrDefault(x => x.Value.Id == key); - var statistics = await _storageStatistics.GetAsync(key); //here is the problem. - - if (thisRound.WinnerId.Equals(thisAccountLogin.Value.Login)) - { - statistics.Wins += 1; - statistics.Score += 4; - } - else - { - statistics.Loss += 1; - - if (statistics.Score == 0) - statistics.Score = 0; - else - { - statistics.Score -= 2; - } - } - - var playerMove = - thisRound.PlayerMoves.FirstOrDefault(x => x.Key.Equals(key)).Value; - switch (playerMove) //NOT TO ADD ANYTHING ELSE - { - case RequiredGameMove.Paper: - statistics.UsedPaper += 1; - break; - case RequiredGameMove.Rock: - statistics.UsedRock += 1; - break; - case RequiredGameMove.Scissors: - statistics.UsedScissors += 1; - break; - } - - if (statistics.Loss != 0) - // ReSharper disable once PossibleLossOfFraction - statistics.WinLossRatio = statistics.Wins / statistics.Loss * 100d; - else - { - statistics.WinLossRatio = 100d; - } - - var allRound = await _storageRounds.GetAllAsync(); - - int wins=0, loss=0; - - foreach (var round in allRound) - { - if (!InRange(round.TimeFinished,DateTime.Now.AddDays(-7), DateTime.Now)) continue; - if (round.WinnerId.Equals(key)) - wins++; - else if (round.LoserId.Equals(key)) - { - loss++; - } - } - - double winRate; - if (loss == 0) - winRate = 0d; - else - { - winRate = (float) wins / loss * 100d; - } - - statistics.TimeSpent = $"Last 7 days win rate: {winRate}%"; - - await _storageStatistics.UpdateAsync(key, statistics); - - } - } - - private static bool InRange(DateTime dateToCheck, DateTime startDate, DateTime endDate) - { - return dateToCheck >= startDate && dateToCheck < endDate; - } - - - //***************************** - private async Task UpdateRound(Round updated) - { - var roomId = - ActiveRounds.Where(x => x.Value - .Equals(updated)).ToArray(); - - if (updated.IsFinished) - { - //if (!updated.PlayerMoves.All(x => x.Key != "Bot") || updated.IsDraw) return updated; - - - //ActiveRounds.TryRemove(roomId[0].Key, out _); - - await Task.FromResult(updated); - return; - } - ActiveRounds.TryGetValue(roomId[0].Key, out var oldRoom); - ActiveRounds.TryUpdate(roomId[0].Key, updated, oldRoom); - - await Task.FromResult(updated); - - } - - //******************************** - public async Task UpdateRound(string roomId) - { - ActiveRounds.TryGetValue(roomId, out var updated); - - if (updated == null) - return null; //todo: add exception; - - await CheckTime(updated); - if (updated.IsFinished) - { - //if(updated.PlayerMoves.Keys.Any(x=> x!="Bot")) - //await _storageRounds.AddAsync(updated); - - //ActiveRounds.TryRemove(roomId, out _); - - return updated; - } - - ActiveRounds.TryGetValue(roomId, out var oldRoom); //Do something with this - ActiveRounds.TryUpdate(roomId, updated, oldRoom); - - return updated; - } - - private async Task CheckTime(Round updated) - { - var elapsedTime = DateTime.Now.Subtract(updated.TimeFinished); - if (elapsedTime.Seconds < 20 || - !updated.PlayerMoves.Any(x => x.Value.Equals(RequiredGameMove.Default))) - return; - var dictionary = updated.PlayerMoves; - var first = dictionary.Keys.First(); - var last = dictionary.Keys.First(); - - if (dictionary[first] == dictionary[last]) - updated.IsDraw = false; - else if (dictionary[first] == RequiredGameMove.Default) - { - updated.LoserId = first; - updated.WinnerId = last; - } - else - { - updated.LoserId = last; - updated.WinnerId = first; - } - updated.TimeFinished = DateTime.Now; - updated.IsFinished = true; - - await UpdateRound(updated); - } - public async Task GetCurrentActiveRoundForSpecialRoom(string roundId) - { - var tasks = Task.Factory.StartNew(() => ActiveRounds.TryGetValue(roundId, out var thisRound) ? thisRound : null); - //todo: change null to exception; - return await tasks; - } - } -} \ No newline at end of file diff --git a/Server/Extensions/DatabaseExtension.cs b/Server/Extensions/DatabaseExtension.cs new file mode 100644 index 0000000..e61a854 --- /dev/null +++ b/Server/Extensions/DatabaseExtension.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Server.Dal.Context; + +namespace Server.Extensions +{ + public static class DatabaseExtension + { + public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) + { + return service.AddDbContext( + builder => builder.UseSqlite + (configuration.GetConnectionString("connectionString"), + x=> x.MigrationsAssembly("Server.Dal"))); + } + } +} \ No newline at end of file diff --git a/Server/Extensions/LoggingMiddleware.cs b/Server/Extensions/LoggingMiddleware.cs index 595d451..3761af6 100644 --- a/Server/Extensions/LoggingMiddleware.cs +++ b/Server/Extensions/LoggingMiddleware.cs @@ -8,19 +8,11 @@ namespace Server.Extensions { - /// - /// Middleware to log raw data - /// internal class LoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; - - /// - /// Constructor - /// - /// - /// + public LoggingMiddleware( RequestDelegate next, ILoggerFactory loggerFactory) @@ -28,12 +20,7 @@ public LoggingMiddleware( _next = next; _logger = loggerFactory.CreateLogger(); } - - /// - /// Invoke method of logging. - /// - /// - /// + public async Task Invoke(HttpContext context) { var requestInformation = $"Request information:\n" + @@ -63,7 +50,6 @@ public async Task Invoke(HttpContext context) private static async Task ObtainRequestBody(HttpRequest request) { - if (request.Body == null) return string.Empty; request.EnableBuffering(); var encoding = GetEncodingFromContentType(request.ContentType); string bodyStr; @@ -101,11 +87,9 @@ private static Encoding GetEncodingFromContentType(string contentTypeStr) { return Encoding.UTF8; } - if (string.IsNullOrEmpty(contentType.CharSet)) - { - return Encoding.UTF8; - } - return Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + return string.IsNullOrEmpty(contentType.CharSet) + ? Encoding.UTF8 + : Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); } private static LogLevel GetLogLevel(int? statusCode) { @@ -113,7 +97,7 @@ private static LogLevel GetLogLevel(int? statusCode) } private static int? GetStatusCode(HttpContext context) { - return context.Response?.StatusCode; + return context.Response.StatusCode; } } diff --git a/Server/Extensions/ServiceExtension.cs b/Server/Extensions/ServiceExtension.cs index 3f833ec..423de3b 100644 --- a/Server/Extensions/ServiceExtension.cs +++ b/Server/Extensions/ServiceExtension.cs @@ -1,8 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; -using Server.GameLogic.LogicServices; -using Server.GameLogic.LogicServices.Interfaces; -using Server.Services; -using Server.Services.Interfaces; +using System; +using Microsoft.Extensions.DependencyInjection; + namespace Server.Extensions { @@ -10,11 +8,8 @@ public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - return service.AddSingleton(typeof(IDeserializedObject<>), typeof(DeserializedObject<>)) - .AddTransient(typeof(IStorage<>), typeof(Storage<>)) - .AddSingleton() - .AddSingleton() - .AddSingleton(); + //return service. + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Server/Extensions/SwaggerExtension.cs b/Server/Extensions/SwaggerExtension.cs new file mode 100644 index 0000000..f772c33 --- /dev/null +++ b/Server/Extensions/SwaggerExtension.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace Server.Extensions +{ + public static class SwaggerExtension + { + //todo: add here documentation + public static IServiceCollection AddSwagger(this IServiceCollection service) + { + return service.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); + }); + } + } +} \ No newline at end of file diff --git a/Server/Startup.cs b/Server/Startup.cs index cdbddf2..f3dec2f 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -22,15 +22,11 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddServices() - .AddDbContext( - builder => builder.UseSqlite - (Configuration.GetConnectionString("connectionString"), - x=> x.MigrationsAssembly("Server.Dal"))); + .AddDatabase(Configuration) + .AddSwagger(); + services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); - }); + services.AddCors(); } From 83f4b3814d5ca612929d3d1f664c4b85a35725c7 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 14 May 2021 20:39:45 +0300 Subject: [PATCH 08/56] Cleared controllers --- Server.Bll/Server.Bll.csproj | 5 + Server/Controllers/AccountController.cs | 51 ++++++++ Server/Controllers/RoomController.cs | 141 ++------------------- Server/Controllers/RoundController.cs | 73 ++--------- Server/Controllers/StatisticsController.cs | 62 ++------- Server/Controllers/UserController.cs | 132 ------------------- 6 files changed, 91 insertions(+), 373 deletions(-) create mode 100644 Server/Controllers/AccountController.cs delete mode 100644 Server/Controllers/UserController.cs diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index cbfa581..d498d62 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -4,4 +4,9 @@ net5.0 + + + + + diff --git a/Server/Controllers/AccountController.cs b/Server/Controllers/AccountController.cs new file mode 100644 index 0000000..400409b --- /dev/null +++ b/Server/Controllers/AccountController.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Server.Contracts; + +namespace Server.Controllers +{ + //todo: add accountDTO + [ApiController] + [Route ("[controller]/[action]")] + [Consumes(MediaTypeNames.Application.Json)] + [Produces(MediaTypeNames.Application.Json)] + public class AccountController : ControllerBase + { + private readonly ILogger _logger; + + public AccountController( + ILogger logger) + { + _logger = logger; + } + [HttpPost] + [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task> Register(AccountDto accountDto) + { + throw new NotImplementedException(); + } + + [HttpPost] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task> Login(AccountDto accountDto) + { + throw new NotImplementedException(); + } + + + + [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] + public async Task> Logout(string sessionId) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Server/Controllers/RoomController.cs b/Server/Controllers/RoomController.cs index b9b0fc0..a642a34 100644 --- a/Server/Controllers/RoomController.cs +++ b/Server/Controllers/RoomController.cs @@ -1,163 +1,50 @@ using Microsoft.AspNetCore.Mvc; -using Server.GameLogic.LogicServices; using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; -using Server.GameLogic.LogicServices.Interfaces; -using Server.GameLogic.Models; namespace Server.Controllers { [ApiController] - [Route("/room")] + [Route("[controller]/[action]")] [Produces(MediaTypeNames.Application.Json)] public class RoomController : ControllerBase { - private readonly IRoomCoordinator _roomManager; - - public RoomController( - IRoomCoordinator roomManager) + + public RoomController() { - _roomManager = roomManager; } [HttpPost] - [Route("create/{sessionId}&{isPrivate}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> CreateRoom(string sessionId, bool isPrivate) + public async Task CreateRoom() { - try - { - var resultRoom = await _roomManager.CreateRoom(sessionId, isPrivate); - if (resultRoom != null) - { - return resultRoom; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - + throw new NotImplementedException(); } [HttpPost] - [Route("join/{sessionId}&{roomId}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> JoinPrivateRoom(string sessionId, string roomId) - { - try - { - var resultRoom = await _roomManager.JoinPrivateRoom(sessionId, roomId); - - if (resultRoom != null) - { - return resultRoom; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - } - - [HttpGet] - [Route("join/{sessionId}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> JoinPublicRoom(string sessionId) + public async Task JoinRoom() { - try - { - var resultRoom = await _roomManager.JoinPublicRoom(sessionId); - - if (resultRoom != null) - { - return resultRoom; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - + throw new NotImplementedException(); } - [HttpPut] - [Route("updateState/{sessionId}&{state}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> UpdatePlayerState(string sessionId, bool state) - { - try - { - var resultRound = await _roomManager.UpdatePlayerStatus(sessionId, state); - if (resultRound != null) - return resultRound; - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - } - [HttpGet] - [Route("updateState/{roomId}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> Update(string roomId) + public async Task UpdateRoom() { - try - { - var resultRound = await _roomManager.UpdateRoom(roomId); - return resultRound; - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } + throw new NotImplementedException(); } - [HttpGet] - [Route("create/training/{sessionId}")] - [ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> CreateTrainingRoom(string sessionId) - { - try - { - var resultRound = await _roomManager.CreateTrainingRoom(sessionId); - return resultRound; - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - } [HttpDelete] - [Route("delete/{roomId}")] - [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] //Probably set new HttpStatus [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> DeleteRoomExit(string roomId) + public async Task DeleteRoom(string roomId) { - try - { - var deleted = await _roomManager.DeleteRoom(roomId); - if (deleted) - return Ok("Room was deleted successfully!"); - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } + throw new NotImplementedException(); } } } diff --git a/Server/Controllers/RoundController.cs b/Server/Controllers/RoundController.cs index 52e2bab..b068991 100644 --- a/Server/Controllers/RoundController.cs +++ b/Server/Controllers/RoundController.cs @@ -1,94 +1,45 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Server.GameLogic.LogicServices; using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; -using Server.GameLogic.LogicServices.Interfaces; -using Server.GameLogic.Models; namespace Server.Controllers { [ApiController] - [Route("/round")] + [Route("[controller]/[action]")] [Produces(MediaTypeNames.Application.Json)] public class RoundController:ControllerBase { + private readonly ILogger _logger; public RoundController( - IRoundCoordinator roundManager, - ILogger logger) + ILogger logger) { - _roundManager = roundManager; _logger = logger; } [HttpGet] - [Route("get/{roomId}")] - [ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> GetActualActiveRoundForCurrentRoom(string roomId) + public async Task GetRound() { - try - { - var resultedRound = await _roundManager.GetCurrentActiveRoundForSpecialRoom(roomId); - if (resultedRound != null) - { - return resultedRound; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } + throw new NotImplementedException(); } [HttpGet] - [Route("get/update/{roomId}")] - [ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> UpdateCurrentRound(string roomId) + public async Task UpdateCurrentRound() { - try - { - var resultedRound = await _roundManager.UpdateRound(roomId); - - if (resultedRound != null) - { - return resultedRound; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - + throw new NotImplementedException(); } [HttpPatch] - [Route("move/{roomId}&{sessionId}&{move}")] - [ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task> PlaceYourMoveToActiveRound(string roomId, string sessionId, int move) + public async Task MakeMove() { - try - { - var resultedRound = await _roundManager.MakeMove(roomId,sessionId,move); - if (resultedRound != null) - { - return resultedRound; - } - return BadRequest(); - } - catch (Exception exception) - { - return BadRequest(exception.Message); - } - + throw new NotImplementedException(); } - - private readonly IRoundCoordinator _roundManager; - private readonly ILogger _logger; } } diff --git a/Server/Controllers/StatisticsController.cs b/Server/Controllers/StatisticsController.cs index 7f48ca4..2b53762 100644 --- a/Server/Controllers/StatisticsController.cs +++ b/Server/Controllers/StatisticsController.cs @@ -1,33 +1,23 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Server.Contracts; -using Server.Mappings; -using Server.Models; -using Server.Services.Interfaces; namespace Server.Controllers { [ApiController] - [Route("/statistics")] - public class OverallStatisticsController : ControllerBase + [Route("[controller]/[action]")] + public class StatisticsController : ControllerBase { - private readonly IDeserializedObject _deserializedStatistics; - private readonly IAccountManager _accountManager; - private readonly ILogger _logger; //todo: add somewhere logger + private readonly ILogger _logger; - public OverallStatisticsController( - IDeserializedObject deserializedStatistics, - IAccountManager accountManager, - ILogger logger) + public StatisticsController( + ILogger logger) { - _deserializedStatistics = deserializedStatistics; - _accountManager = accountManager; _logger = logger; } @@ -37,49 +27,15 @@ public OverallStatisticsController( [ProducesResponseType((int) HttpStatusCode.BadRequest)] public ActionResult> GetOverallStatistics() { - try - { - var deserialized = _deserializedStatistics.ConcurrentDictionary.Values; - - var statisticsList = deserialized.Where(x => x.Score > 10).AsParallel().ToArray(); - - if (statisticsList.Length < 1) - return NotFound(); - - var resultList = statisticsList.Select(statistics => statistics.ToStatisticsDto()).ToList(); - - return resultList; - } - catch (Exception exceptions) //todo: custom exception OR NOT :) - { - return BadRequest(exceptions.Message); - } - + throw new NotImplementedException(); } - [HttpGet] - [Route("personalStatistics/{sessionId}")] - [ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] + //[ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public ActionResult> GetPersonalStatistics(string sessionId) + public ActionResult GetPersonalStatistics() { - try - { - var deserialized = _deserializedStatistics.ConcurrentDictionary.Values; - - var thisUserId = _accountManager.AccountsActive - .FirstOrDefault(x => x.Key.Equals(sessionId)).Value.Id; - - var resultStatistics = deserialized.FirstOrDefault(x => x.Id.Equals(thisUserId)); - - return Ok(resultStatistics); - } - catch (Exception exceptions) //todo: custom exception - { - return BadRequest(exceptions.Message); - } - + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Server/Controllers/UserController.cs b/Server/Controllers/UserController.cs deleted file mode 100644 index 186700f..0000000 --- a/Server/Controllers/UserController.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Net.Mime; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Server.Contracts; -using Server.Exceptions.LogIn; -using Server.Exceptions.Register; -using Server.GameLogic.LogicServices.Interfaces; -using Server.Models; -using Server.Services.Interfaces; - -namespace Server.Controllers -{ - [ApiController] - [Route ("/user")] - [Consumes(MediaTypeNames.Application.Json)] - [Produces(MediaTypeNames.Application.Json)] - public class UserController : ControllerBase - { - private readonly IStorage _accountStorage; - - private readonly IStorage _statisticsStorage; //Just to write new statistics field into file along with account - - private readonly ILogger _logger; - - private readonly IAccountManager _accountManager; - public UserController( - IStorage users, - IStorage statisticsStorage, - IAccountManager accountManager, - ILogger logger, - IRoomCoordinator roomCoordinator - ) - { - _accountStorage = users; - _statisticsStorage = statisticsStorage; - _accountManager = accountManager; - _logger = logger; - } - /// - /// Method to log in an account - /// - /// Data Transfer Object of account - /// Status code and response string - [HttpPost] - [Route("login")] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task> Login(AccountDto accountDto) - { - try - { - await _accountManager.LogInAsync(accountDto); - return Ok(accountDto); - - } - catch (ValidationException exception) - { - _logger.Log(LogLevel.Warning,"Validation error"); - return BadRequest(exception.Message); - } - catch (LoginErrorException exception) - { - _logger.Log(LogLevel.Error,exception.Message); - return BadRequest(exception.Message); - } - - } - - /// - /// Method to register a new account - /// - /// Data Transfer Object of account - /// HttpStatusCode and response string - [HttpPost] - [Route("register")] - [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task> CreateAccount(AccountDto accountDto) - { - try - { - var account = new Account - { - Id = Guid.NewGuid().ToString(), - Login = accountDto.Login, - Password = accountDto.Password - }; - var statistics = new Statistics - { - Id = account.Id, - Login = account.Login, - }; - - await _accountStorage.AddAsync(account); - await _statisticsStorage.AddAsync(statistics); - - return Created("", $"Account [{account.Login}] successfully created"); - } - catch (ValidationException exception) - { - _logger.Log(LogLevel.Warning,exception.Message); - return BadRequest(exception.Message); - } - catch (UnknownReasonException exception) - { - _logger.Log(LogLevel.Warning, exception.Message); - return BadRequest(exception.Message); - } - } - - /// - /// Method to Log out of account - /// - /// Session id of a client - /// HttpStatusCode - [Route("logout")] - [HttpGet("logout/{sessionId}")] - [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] - public async Task> LogOut(string sessionId) - { - var result = await _accountManager.LogOutAsync(sessionId); - if (result) - return (int) HttpStatusCode.OK; - return (int) HttpStatusCode.Forbidden; - } - } -} \ No newline at end of file From 1c98c2f673fc4a7924f7ab2966ee13836725a4b3 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 14 May 2021 21:09:58 +0300 Subject: [PATCH 09/56] Added memorycache --- Server.Bll/Server.Bll.csproj | 4 +++ Server.Bll/Services/AccountService.cs | 29 +++++++++++++++++++ .../Services/Interfaces/IAccountService.cs | 9 +++--- Server/Contracts/Requests/LoginRequest.cs | 17 +++++++++++ Server/Contracts/Requests/RegisterRequest.cs | 13 +++++++++ .../Contracts/ViewModels/AccountViewModel.cs | 9 ++++++ Server/Controllers/AccountController.cs | 23 ++++++++++++--- Server/Extensions/ServiceExtension.cs | 5 +++- Server/Server.csproj | 5 ++++ 9 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 Server.Bll/Services/AccountService.cs create mode 100644 Server/Contracts/Requests/LoginRequest.cs create mode 100644 Server/Contracts/Requests/RegisterRequest.cs create mode 100644 Server/Contracts/ViewModels/AccountViewModel.cs diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index d498d62..c467c6d 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/Server.Bll/Services/AccountService.cs b/Server.Bll/Services/AccountService.cs new file mode 100644 index 0000000..5d7243c --- /dev/null +++ b/Server.Bll/Services/AccountService.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; +using Server.Exceptions.LogIn; + +namespace Server.Bll.Services +{ + public class AccountService : IAccountService + { + private readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions()); + public Task RegisterAsync(AccountModel accountModel) + { + if (accountModel is null) + throw new InvalidCredentialsException(nameof(accountModel)); + + } + + public Task LogInAsync(AccountModel accountModel) + { + throw new System.NotImplementedException(); + } + + public Task LogOutAsync(string accountToken) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IAccountService.cs b/Server.Bll/Services/Interfaces/IAccountService.cs index c638c57..477eeaa 100644 --- a/Server.Bll/Services/Interfaces/IAccountService.cs +++ b/Server.Bll/Services/Interfaces/IAccountService.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; +using Server.Bll.Models; -namespace Server.Services.Interfaces +namespace Server.Bll.Services.Interfaces { public interface IAccountService { - Task RegisterAsync(); - Task LogInAsync(); - Task LogOutAsync(); + Task RegisterAsync(AccountModel accountModel); + Task LogInAsync(AccountModel accountModel); + Task LogOutAsync(string accountToken); } } \ No newline at end of file diff --git a/Server/Contracts/Requests/LoginRequest.cs b/Server/Contracts/Requests/LoginRequest.cs new file mode 100644 index 0000000..7a095be --- /dev/null +++ b/Server/Contracts/Requests/LoginRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Server.Contracts.Requests +{ + public class LoginRequest + { + [Required(ErrorMessage = "Login is required!")] + public string Login { get; set; } + + [Required(ErrorMessage = "Password is required!!")] + [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] + public string Password { get; set; } + + public DateTimeOffset LastRequestTime { get; set; } + } +} \ No newline at end of file diff --git a/Server/Contracts/Requests/RegisterRequest.cs b/Server/Contracts/Requests/RegisterRequest.cs new file mode 100644 index 0000000..3d6cb35 --- /dev/null +++ b/Server/Contracts/Requests/RegisterRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Server.Contracts.Requests +{ + public class RegisterRequest + { + [Required(ErrorMessage = "Login is required!")] + public string Login { get; set; } + + [Required(ErrorMessage = "Password is required!")] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/Server/Contracts/ViewModels/AccountViewModel.cs b/Server/Contracts/ViewModels/AccountViewModel.cs new file mode 100644 index 0000000..8e2ec39 --- /dev/null +++ b/Server/Contracts/ViewModels/AccountViewModel.cs @@ -0,0 +1,9 @@ +namespace Server.Contracts.ViewModels +{ + public class AccountViewModel + { + public string Token { get; set; } + + public string Login { get; set; } + } +} \ No newline at end of file diff --git a/Server/Controllers/AccountController.cs b/Server/Controllers/AccountController.cs index 400409b..fc349d3 100644 --- a/Server/Controllers/AccountController.cs +++ b/Server/Controllers/AccountController.cs @@ -3,13 +3,17 @@ using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Mapster; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; using Server.Contracts; +using Server.Contracts.Requests; +using Server.Contracts.ViewModels; namespace Server.Controllers { - //todo: add accountDTO [ApiController] [Route ("[controller]/[action]")] [Consumes(MediaTypeNames.Application.Json)] @@ -17,18 +21,29 @@ namespace Server.Controllers public class AccountController : ControllerBase { private readonly ILogger _logger; + private readonly IAccountService _accountService; public AccountController( - ILogger logger) + ILogger logger, + IAccountService accountService) { _logger = logger; + _accountService = accountService; } [HttpPost] [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task> Register(AccountDto accountDto) + public async Task Register(RegisterRequest registerRequest) { - throw new NotImplementedException(); + try + { + var result = _accountService.RegisterAsync(registerRequest.Adapt()); + return Ok(await result); + } + catch (Exception exception) + { + return BadRequest(); + } } [HttpPost] diff --git a/Server/Extensions/ServiceExtension.cs b/Server/Extensions/ServiceExtension.cs index 423de3b..2b2b43b 100644 --- a/Server/Extensions/ServiceExtension.cs +++ b/Server/Extensions/ServiceExtension.cs @@ -1,5 +1,7 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Server.Bll.Services; +using Server.Bll.Services.Interfaces; namespace Server.Extensions @@ -8,7 +10,8 @@ public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - //return service. + return service. + AddSingleton(); throw new NotImplementedException(); } } diff --git a/Server/Server.csproj b/Server/Server.csproj index 1d73ec0..547d420 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -24,8 +24,13 @@ + + + + + From 18f7cea2d6f269eca12b68f4e20bee12acdc4d44 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Thu, 10 Jun 2021 22:52:39 +0300 Subject: [PATCH 10/56] Initial addition of JWT --- RockPaperScissorsGame.sln | 11 +++++++ Server.Authentication/AuthExtension.cs | 30 +++++++++++++++++++ Server.Authentication/AuthOptions.cs | 18 +++++++++++ .../Server.Authentication.csproj | 11 +++++++ Server.Bll/Services/AccountService.cs | 9 +++--- Server/Extensions/DatabaseExtension.cs | 6 ++-- Server/Extensions/LoggingMiddleware.cs | 15 +++++----- Server/Extensions/ServiceExtension.cs | 1 - Server/Extensions/SwaggerExtension.cs | 3 +- Server/Server.csproj | 4 --- Server/appsettings.json | 2 +- 11 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 Server.Authentication/AuthExtension.cs create mode 100644 Server.Authentication/AuthOptions.cs create mode 100644 Server.Authentication/Server.Authentication.csproj diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 487c114..6d23154 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Dal", "Server.Dal\Se EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server.Bll\Server.Bll.csproj", "{FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libs", "Libs", "{09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Authentication", "Server.Authentication\Server.Authentication.csproj", "{004A30E1-2903-4C9F-8D02-EEBE67C677BD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +37,10 @@ Global {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Debug|Any CPU.Build.0 = Debug|Any CPU {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.ActiveCfg = Release|Any CPU {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.Build.0 = Release|Any CPU + {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -40,4 +48,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4ADC353B-52B1-43B2-B26D-3279A6ACC978} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {004A30E1-2903-4C9F-8D02-EEBE67C677BD} = {09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8} + EndGlobalSection EndGlobal diff --git a/Server.Authentication/AuthExtension.cs b/Server.Authentication/AuthExtension.cs new file mode 100644 index 0000000..422af5c --- /dev/null +++ b/Server.Authentication/AuthExtension.cs @@ -0,0 +1,30 @@ +using System.Net.NetworkInformation; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; + +namespace Server.Authentication +{ + public static class AuthExtension + { + public static IServiceCollection AddAuthentication(this IServiceCollection services) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = false; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = AuthOptions.Issuer, + ValidateAudience = true, + ValidAudience = AuthOptions.Audience, + ValidateLifetime = true, + IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(), + ValidateIssuerSigningKey = true + }; + }); + return services; + } + } +} \ No newline at end of file diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs new file mode 100644 index 0000000..5a700dc --- /dev/null +++ b/Server.Authentication/AuthOptions.cs @@ -0,0 +1,18 @@ +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace Server.Authentication +{ + public class AuthOptions + { + public const string Issuer = "RPSGame"; + public const string Audience = "RPSPlayer"; + private const string Key = "RockPaperScissors"; + public const int Lifetime = 10; + + public static SymmetricSecurityKey GetSymmetricSecurityKey() + { + return new(Encoding.ASCII.GetBytes(Key)); + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj new file mode 100644 index 0000000..6f2a731 --- /dev/null +++ b/Server.Authentication/Server.Authentication.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/Server.Bll/Services/AccountService.cs b/Server.Bll/Services/AccountService.cs index 5d7243c..48399a2 100644 --- a/Server.Bll/Services/AccountService.cs +++ b/Server.Bll/Services/AccountService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -11,9 +12,9 @@ public class AccountService : IAccountService private readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions()); public Task RegisterAsync(AccountModel accountModel) { - if (accountModel is null) - throw new InvalidCredentialsException(nameof(accountModel)); - + /*if (accountModel is null) + throw new InvalidCredentialsException(nameof(accountModel));*/ + throw new NotImplementedException(); } public Task LogInAsync(AccountModel accountModel) diff --git a/Server/Extensions/DatabaseExtension.cs b/Server/Extensions/DatabaseExtension.cs index e61a854..097ab72 100644 --- a/Server/Extensions/DatabaseExtension.cs +++ b/Server/Extensions/DatabaseExtension.cs @@ -7,12 +7,14 @@ namespace Server.Extensions { public static class DatabaseExtension { + private const string DatabaseConnection = "DatabaseConnection"; + private const string MigrationAssemblyName = "Server.Dal"; public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { return service.AddDbContext( builder => builder.UseSqlite - (configuration.GetConnectionString("connectionString"), - x=> x.MigrationsAssembly("Server.Dal"))); + (configuration.GetConnectionString(DatabaseConnection), + x=> x.MigrationsAssembly(MigrationAssemblyName))); } } } \ No newline at end of file diff --git a/Server/Extensions/LoggingMiddleware.cs b/Server/Extensions/LoggingMiddleware.cs index 3761af6..d19c2dd 100644 --- a/Server/Extensions/LoggingMiddleware.cs +++ b/Server/Extensions/LoggingMiddleware.cs @@ -23,13 +23,14 @@ public LoggingMiddleware( public async Task Invoke(HttpContext context) { - var requestInformation = $"Request information:\n" + - $"Schema:{context.Request.Scheme}\n" + - $"Content-Type:{context.Request.ContentType}" + - $"Host:{context.Request.Host}\n" + - $"Path:{context.Request.Path}\n" + - $"QueryString:{context.Request.QueryString}\n" + - $"Request Body:{await ObtainRequestBody(context.Request)}\n"; + var requestInformation = + "Request information:\n" + + $"Schema:{context.Request.Scheme}\n" + + $"Content-Type:{context.Request.ContentType}" + + $"Host:{context.Request.Host}\n" + + $"Path:{context.Request.Path}\n" + + $"QueryString:{context.Request.QueryString}\n" + + $"Request Body:{await ObtainRequestBody(context.Request)}\n"; _logger.LogWarning(requestInformation); var originalResponseBody = context.Response.Body; diff --git a/Server/Extensions/ServiceExtension.cs b/Server/Extensions/ServiceExtension.cs index 2b2b43b..72d3f73 100644 --- a/Server/Extensions/ServiceExtension.cs +++ b/Server/Extensions/ServiceExtension.cs @@ -12,7 +12,6 @@ public static IServiceCollection AddServices(this IServiceCollection service) { return service. AddSingleton(); - throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Server/Extensions/SwaggerExtension.cs b/Server/Extensions/SwaggerExtension.cs index f772c33..c90d19a 100644 --- a/Server/Extensions/SwaggerExtension.cs +++ b/Server/Extensions/SwaggerExtension.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; namespace Server.Extensions diff --git a/Server/Server.csproj b/Server/Server.csproj index 547d420..b5255b5 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -28,9 +28,5 @@ - - - - diff --git a/Server/appsettings.json b/Server/appsettings.json index 419e7d7..c54b648 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "connectionString": "Host=localhost;Port=5433;Database=sqlite;Username=sqlite;Password=sqlite" + "DatabaseConnection": "Host=localhost;Port=5433;Database=sqlite;Username=sqlite;Password=sqlite" }, "Logging": { "LogLevel": { From 04c206bc39e6664ca852bc3e172c49b8634d6d1c Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Sun, 11 Jul 2021 23:27:40 +0300 Subject: [PATCH 11/56] Added and COMPLETELY changed Authorization. Added new cool feature OneOf --- .../RequestProcessor/Impl/RequestHandler.cs | 2 +- RockPaperScissorsGame.sln | 2 +- Server.Authentication/AuthExtension.cs | 30 -- Server.Authentication/AuthOptions.cs | 45 ++- .../Exceptions/UserException.cs | 23 ++ .../Exceptions/UserExceptionsTemplates.cs | 11 + .../Extensions/AuthenticationExtension.cs | 57 ++++ Server.Authentication/HashingBase64.cs | 28 ++ .../Models/AccountOutputModel.cs | 8 + Server.Authentication/Models/AuthUser.cs | 35 +++ Server.Authentication/Models/IAuthUser.cs | 13 + Server.Authentication/Models/Roles.cs | 17 ++ .../Server.Authentication.csproj | 5 + .../Services/AttemptValidationService.cs | 46 +++ Server.Authentication/Services/AuthService.cs | 155 ++++++++++ .../Services/IAuthService.cs | 14 + .../Exceptions/AlreadyExistsException.cs | 2 +- .../Exceptions/InvalidCredentialsException.cs | 2 +- .../Exceptions/LoginCooldownException.cs | 2 +- Server.Bll/Exceptions/LoginErrorException.cs | 2 +- .../TwinkGameRoomCreationException.cs | 2 +- .../Exceptions/UnknownReasonException.cs | 2 +- .../UserAlreadySignedInException.cs | 2 +- .../Exceptions/UserNotFoundException.cs | 7 +- Server.Bll/Models/AccountModel.cs | 2 +- Server.Bll/Services/AccountService.cs | 30 -- .../Services/Interfaces/IAccountService.cs | 12 - .../Services/Interfaces/IRoundService.cs | 2 +- Server.Dal/Entities/Account.cs | 8 +- Server.Dal/Entities/Room.cs | 6 +- Server.Dal/Entities/RoomPlayers.cs | 6 +- Server.Dal/Entities/Round.cs | 11 +- Server.Dal/Entities/Statistics.cs | 6 +- Server.Dal/IRepository.cs | 18 -- .../20210509231534_InitialCommit.Designer.cs | 249 ---------------- .../20210510122522_FixEntities.Designer.cs | 265 ------------------ .../Migrations/20210510122522_FixEntities.cs | 55 ---- .../Migrations/20210514162359_PlayerMoves.cs | 35 --- ... 20210711202013_InitialCommit.Designer.cs} | 86 +++--- ...mit.cs => 20210711202013_InitialCommit.cs} | 124 ++++---- .../Migrations/ServerContextModelSnapshot.cs | 82 +++--- Server.Dal/Repository.cs | 71 ----- {Server => Server.Host}/Account | 0 .../Contracts/AccountDto.cs | 3 +- .../Contracts/Requests/LoginRequest.cs | 0 .../Contracts/Requests/RegisterRequest.cs | 1 + .../Contracts/StatisticsDto.cs | 0 .../Contracts/ViewModels/AccountViewModel.cs | 0 Server.Host/Controllers/AccountController.cs | 61 ++++ .../Controllers/RoomController.cs | 7 +- .../Controllers/RoundController.cs | 6 +- .../Controllers/StatisticsController.cs | 0 .../Extensions/DatabaseExtension.cs | 7 +- .../Extensions/LoggingMiddleware.cs | 2 +- .../Extensions/ServiceExtension.cs | 8 +- Server.Host/Extensions/SwaggerExtension.cs | 63 +++++ {Server => Server.Host}/Program.cs | 4 +- .../Properties/launchSettings.json | 0 .../Server.Host.csproj | 2 + {Server => Server.Host}/Startup.cs | 14 +- .../appsettings.Development.json | 0 {Server => Server.Host}/appsettings.json | 2 +- Server/Controllers/AccountController.cs | 66 ----- Server/Extensions/SwaggerExtension.cs | 17 -- Server/Statistics | 1 - 65 files changed, 810 insertions(+), 1034 deletions(-) delete mode 100644 Server.Authentication/AuthExtension.cs create mode 100644 Server.Authentication/Exceptions/UserException.cs create mode 100644 Server.Authentication/Exceptions/UserExceptionsTemplates.cs create mode 100644 Server.Authentication/Extensions/AuthenticationExtension.cs create mode 100644 Server.Authentication/HashingBase64.cs create mode 100644 Server.Authentication/Models/AccountOutputModel.cs create mode 100644 Server.Authentication/Models/AuthUser.cs create mode 100644 Server.Authentication/Models/IAuthUser.cs create mode 100644 Server.Authentication/Models/Roles.cs create mode 100644 Server.Authentication/Services/AttemptValidationService.cs create mode 100644 Server.Authentication/Services/AuthService.cs create mode 100644 Server.Authentication/Services/IAuthService.cs delete mode 100644 Server.Bll/Services/AccountService.cs delete mode 100644 Server.Bll/Services/Interfaces/IAccountService.cs delete mode 100644 Server.Dal/IRepository.cs delete mode 100644 Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs delete mode 100644 Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs delete mode 100644 Server.Dal/Migrations/20210510122522_FixEntities.cs delete mode 100644 Server.Dal/Migrations/20210514162359_PlayerMoves.cs rename Server.Dal/Migrations/{20210514162359_PlayerMoves.Designer.cs => 20210711202013_InitialCommit.Designer.cs} (75%) rename Server.Dal/Migrations/{20210509231534_InitialCommit.cs => 20210711202013_InitialCommit.cs} (73%) delete mode 100644 Server.Dal/Repository.cs rename {Server => Server.Host}/Account (100%) rename {Server => Server.Host}/Contracts/AccountDto.cs (79%) rename {Server => Server.Host}/Contracts/Requests/LoginRequest.cs (100%) rename {Server => Server.Host}/Contracts/Requests/RegisterRequest.cs (77%) rename {Server => Server.Host}/Contracts/StatisticsDto.cs (100%) rename {Server => Server.Host}/Contracts/ViewModels/AccountViewModel.cs (100%) create mode 100644 Server.Host/Controllers/AccountController.cs rename {Server => Server.Host}/Controllers/RoomController.cs (86%) rename {Server => Server.Host}/Controllers/RoundController.cs (96%) rename {Server => Server.Host}/Controllers/StatisticsController.cs (100%) rename {Server => Server.Host}/Extensions/DatabaseExtension.cs (71%) rename {Server => Server.Host}/Extensions/LoggingMiddleware.cs (99%) rename {Server => Server.Host}/Extensions/ServiceExtension.cs (55%) create mode 100644 Server.Host/Extensions/SwaggerExtension.cs rename {Server => Server.Host}/Program.cs (90%) rename {Server => Server.Host}/Properties/launchSettings.json (100%) rename Server/Server.csproj => Server.Host/Server.Host.csproj (90%) rename {Server => Server.Host}/Startup.cs (82%) rename {Server => Server.Host}/appsettings.Development.json (100%) rename {Server => Server.Host}/appsettings.json (67%) delete mode 100644 Server/Controllers/AccountController.cs delete mode 100644 Server/Extensions/SwaggerExtension.cs delete mode 100644 Server/Statistics diff --git a/Client/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Services/RequestProcessor/Impl/RequestHandler.cs index d9e18dc..4f5bc05 100644 --- a/Client/Services/RequestProcessor/Impl/RequestHandler.cs +++ b/Client/Services/RequestProcessor/Impl/RequestHandler.cs @@ -49,7 +49,7 @@ public async Task HandleRequestAsync(IRequestOptions requestOptions) } catch (HttpRequestException) //todo: probably redo { - return new Response(false, 500, "Server is not responding!"); + return new Response(false, 500, "Server.Host is not responding!"); } } private static HttpMethod MapMethod(RequestMethod method) diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 6d23154..060574d 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{53166F83-4032-4CCF-B172-C2517BFBA424}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Host", "Server.Host\Server.Host.csproj", "{53166F83-4032-4CCF-B172-C2517BFBA424}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{B6D69162-F9E0-4165-A288-00FF1D073291}" EndProject diff --git a/Server.Authentication/AuthExtension.cs b/Server.Authentication/AuthExtension.cs deleted file mode 100644 index 422af5c..0000000 --- a/Server.Authentication/AuthExtension.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Net.NetworkInformation; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; - -namespace Server.Authentication -{ - public static class AuthExtension - { - public static IServiceCollection AddAuthentication(this IServiceCollection services) - { - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.RequireHttpsMetadata = false; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = AuthOptions.Issuer, - ValidateAudience = true, - ValidAudience = AuthOptions.Audience, - ValidateLifetime = true, - IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(), - ValidateIssuerSigningKey = true - }; - }); - return services; - } - } -} \ No newline at end of file diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index 5a700dc..00687b7 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -1,18 +1,49 @@ -using System.Text; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; using Microsoft.IdentityModel.Tokens; +using Server.Authentication.Models; namespace Server.Authentication { + /// + /// Options for JWT token + /// public class AuthOptions { - public const string Issuer = "RPSGame"; - public const string Audience = "RPSPlayer"; - private const string Key = "RockPaperScissors"; - public const int Lifetime = 10; + /// + /// Token issuer (producer). + /// + public string Issuer { get; set; } = "RPC"; - public static SymmetricSecurityKey GetSymmetricSecurityKey() + /// + /// Token audience (consumer). + /// + public string Audience { get; set; } = "Server.Host"; + + /// + /// Token secret part. + /// + public string PrivateKey { get; set; } = "RockPaperScissors"; + + /// + /// Token life time. + /// + public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3d); + + /// + /// Require HTTPS. + /// + public bool RequireHttps { get; set; } = false; + + /// + /// Getting a symmetric security key + /// + /// + public SymmetricSecurityKey GetSymmetricSecurityKey() { - return new(Encoding.ASCII.GetBytes(Key)); + return new(Encoding.ASCII.GetBytes(PrivateKey)); } } } \ No newline at end of file diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs new file mode 100644 index 0000000..36173b6 --- /dev/null +++ b/Server.Authentication/Exceptions/UserException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; + +namespace Server.Authentication.Exceptions +{ + public readonly struct UserException + { + public int Code { get; } + public string Message { get; } + + public UserException(string messageType, string message, DateTimeOffset dateTimeOffset) + { + Code = (int) HttpStatusCode.BadRequest; + Message = string.Format(messageType, message, dateTimeOffset); + } + + public UserException(string invalidCredentialsForUser, string loginName) + { + Code = (int) HttpStatusCode.BadRequest; + Message = string.Format(invalidCredentialsForUser, loginName); + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs new file mode 100644 index 0000000..7935252 --- /dev/null +++ b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -0,0 +1,11 @@ +namespace Server.Authentication.Exceptions +{ + public static class UserExceptionsTemplates + { + public const string UserCoolDown = "User [{0}] has been cooled down till [{1}]."; + public const string UserInvalidCredentials = "Invalid Crenedtials for user [{0}]."; + public const string UserNotFound = "User with login [{0}] is not found."; + public const string UserAlreadyExists = "User with login [{0}] already exists."; + public const string UnknownError = "Unknown error occured. Please try again."; + } +} \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs new file mode 100644 index 0000000..12f3720 --- /dev/null +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -0,0 +1,57 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Server.Authentication.Services; + +namespace Server.Authentication.Extensions +{ + /// + /// Authentication services extension + /// + public static class AuthenticationExtension + { + /// + /// Register authentication + /// + /// Service collection + /// + public static IServiceCollection AddAuthentications(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddOptions(); + var jwtOptions = services + .BuildServiceProvider() + .GetRequiredService>() + .Value; + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = jwtOptions.RequireHttps; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtOptions.Issuer, + + ValidateAudience = true, + ValidAudience = jwtOptions.Audience, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, + + IssuerSigningKey = jwtOptions.GetSymmetricSecurityKey(), + ValidateIssuerSigningKey = true + }; + }); + + services + .AddTransient() + .AddSingleton(typeof(AttemptValidationService)); + return services; + } + } +} \ No newline at end of file diff --git a/Server.Authentication/HashingBase64.cs b/Server.Authentication/HashingBase64.cs new file mode 100644 index 0000000..0588797 --- /dev/null +++ b/Server.Authentication/HashingBase64.cs @@ -0,0 +1,28 @@ +using System; + +namespace Server.Authentication +{ + public static class HashingBase64 + { + public static string DecodeBase64(this string encodedData) + { + var encodedDataAsBytes + = System.Convert.FromBase64String(encodedData); + return + System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); + } + + public static string EncodeBase64(this string decodedData) + { + var dataArray = System.Text.Encoding.ASCII.GetBytes(decodedData); + + return System.Convert.ToBase64String(dataArray); + } + + public static bool IsHashEqual(this string base64Data, string initialData) + { + var baseDecoded = EncodeBase64(initialData); + return base64Data.Equals(baseDecoded, StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/AccountOutputModel.cs b/Server.Authentication/Models/AccountOutputModel.cs new file mode 100644 index 0000000..fabbd5a --- /dev/null +++ b/Server.Authentication/Models/AccountOutputModel.cs @@ -0,0 +1,8 @@ +namespace Server.Authentication.Models +{ + public class AccountOutputModel + { + public string Token { get; set; } + public string Login { get; set; } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/AuthUser.cs b/Server.Authentication/Models/AuthUser.cs new file mode 100644 index 0000000..412828d --- /dev/null +++ b/Server.Authentication/Models/AuthUser.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Microsoft.AspNetCore.Http; + +namespace Server.Authentication.Models +{ + /// + /// Application user + /// + public class AuthUser: IAuthUser + { + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// Constructor + /// + /// http accessor + public AuthUser(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// This user шв + /// + public int Id => GetUserId(); + + private int GetUserId() + { + var request = _httpContextAccessor.HttpContext + ?.User.Claims.FirstOrDefault(x => x.Type == "id"); + + return int.TryParse(request?.Value, out var id) ? id : 0; + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/IAuthUser.cs b/Server.Authentication/Models/IAuthUser.cs new file mode 100644 index 0000000..f3e4566 --- /dev/null +++ b/Server.Authentication/Models/IAuthUser.cs @@ -0,0 +1,13 @@ +namespace Server.Authentication.Models +{ + /// + /// Interface for ApplicationUser class + /// + public interface IAuthUser + { + /// + /// User request id from token + /// + int Id { get; } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/Roles.cs b/Server.Authentication/Models/Roles.cs new file mode 100644 index 0000000..c113034 --- /dev/null +++ b/Server.Authentication/Models/Roles.cs @@ -0,0 +1,17 @@ +namespace Server.Authentication.Models +{ + /// + /// User roles + /// + public static class Roles + { + /// + /// Admin role + /// + public const string Admin = "admin"; + /// + /// User role + /// + public const string User = "user"; + } +} \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 6f2a731..0399183 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -6,6 +6,11 @@ + + + + + diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs new file mode 100644 index 0000000..4024e7e --- /dev/null +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace Server.Authentication.Services +{ + public class AttemptValidationService + { + // key - userId. Value - failed attempts + private readonly ConcurrentDictionary _failedAttempts = new(); + private readonly ConcurrentDictionary _coolDownCollection = new(); + + public Task InsertFailAttempt(string userId) + { + if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) + { + if (failedAttempts >= 2) + { + // todo: in options + _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); + _failedAttempts.TryRemove(userId, out _); + return Task.CompletedTask; + } + } + _failedAttempts.AddOrUpdate(userId, 1, (s, i) => i + 1); + + return Task.CompletedTask; + } + + public Task CountFailedAttempts(string userId) + { + _failedAttempts.TryGetValue(userId, out var failedAttempts); + return Task.FromResult(failedAttempts); + } + + public Task IsCoolDown(string userId, out DateTimeOffset coolDownDate) + { + var result = _coolDownCollection.TryGetValue(userId, out coolDownDate); + if (!result) return Task.FromResult(false); + if (coolDownDate >= DateTimeOffset.Now) return Task.FromResult(true); + _coolDownCollection.TryRemove(userId, out coolDownDate); + return Task.FromResult(false); + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs new file mode 100644 index 0000000..64531bf --- /dev/null +++ b/Server.Authentication/Services/AuthService.cs @@ -0,0 +1,155 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using OneOf; +using Server.Authentication.Exceptions; +using Server.Authentication.Models; +using Server.Dal.Context; +using Server.Dal.Entities; + +namespace Server.Authentication.Services +{ + /// + /// Implements . + /// + internal class AuthService : IAuthService + { + private readonly ServerContext _repository; + private readonly AttemptValidationService _attemptValidationService; + private readonly AuthOptions _authOptions; + private readonly ILogger _logger; + private readonly SemaphoreSlim _semaphore = new(1, 1); + + private readonly DbSet _accounts; + + + public AuthService( + ILogger logger, + IOptions authOptions, + AttemptValidationService attemptValidationService, + ServerContext repository) + { + _logger = logger; + _attemptValidationService = attemptValidationService; + _repository = repository; + _authOptions = authOptions.Value; + _accounts = repository.Accounts; + } + + /// + public async Task> + RegisterAsync(string login, string password) + { + if (login == null) return new UserException(UserExceptionsTemplates.UserInvalidCredentials ,nameof(login)); + if (password == null) return new UserException(UserExceptionsTemplates.UserInvalidCredentials ,nameof(password)); + + var release = await _semaphore.WaitAsync(1000); + try + { + if (await _accounts.CountAsync(x=>x.Login == login) > 0) + return new UserException(UserExceptionsTemplates.UserAlreadyExists,login); + + var account = new Account + { + Login = login, + Password = password.EncodeBase64() + }; + await _accounts.AddAsync(account); + + await _repository.SaveChangesAsync(); + + account = await _accounts.FirstOrDefaultAsync(x => x.Login == login); + var accountStatistics = new Statistics + { + AccountId = account.Id + }; + await _repository.StatisticsEnumerable.AddAsync(accountStatistics); + + await _repository.SaveChangesAsync(); + return 200; + } + catch + { + _logger.LogWarning("Unable to process account for {0}", login); + return new UserException(UserExceptionsTemplates.UnknownError,string.Empty); + } + finally + { + if (release) + _semaphore.Release(); + } + } + + /// + //public async Task<(AccountOutputModel,DateTimeOffset)> + public async Task> LoginAsync(string login, string password) + { + var userAccount = await _accounts.FirstOrDefaultAsync(x => x.Login == login); + + if (userAccount is null) + { + return new UserException(UserExceptionsTemplates.UserNotFound,login); + } + + if (await _attemptValidationService.IsCoolDown(login, out var coolRequestDate)) + return new UserException(UserExceptionsTemplates.UserCoolDown,login,coolRequestDate); + + if (userAccount.Password.IsHashEqual(login)) + return new AccountOutputModel + { + Token = BuildToken(userAccount), + Login = userAccount.Login + }; + + await _attemptValidationService.InsertFailAttempt(login); + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, login); + } + + public Task RemoveAsync(int accountId) + { + throw new NotImplementedException(); + } + + private string BuildToken(Account accountModel) + { + var identity = GetClaimsIdentity(accountModel.Id); + + var now = DateTime.UtcNow; + var jwtToken = new JwtSecurityToken( + issuer: _authOptions.Issuer, + audience: _authOptions.Audience, + notBefore: now, + claims: identity.Claims, + expires: now.Add(_authOptions.LifeTime), + signingCredentials: new SigningCredentials( + _authOptions.GetSymmetricSecurityKey(), + SecurityAlgorithms.HmacSha256)); + + var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwtToken); + + return encodedJwt; + } + + private static ClaimsIdentity GetClaimsIdentity(int userId) + { + var claims = new[] + { + new Claim("id", userId.ToString()), + new Claim(ClaimsIdentity.DefaultRoleClaimType, "User") + }; + var claimsIdentity = + new ClaimsIdentity( + claims, + "Token", + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType); + return claimsIdentity; + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Services/IAuthService.cs b/Server.Authentication/Services/IAuthService.cs new file mode 100644 index 0000000..4372fb7 --- /dev/null +++ b/Server.Authentication/Services/IAuthService.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using OneOf; +using Server.Authentication.Exceptions; +using Server.Authentication.Models; + +namespace Server.Authentication.Services +{ + public interface IAuthService + { + Task> RegisterAsync(string login, string password); + Task> LoginAsync(string login, string password); + } +} \ No newline at end of file diff --git a/Server.Bll/Exceptions/AlreadyExistsException.cs b/Server.Bll/Exceptions/AlreadyExistsException.cs index 0861364..62409a7 100644 --- a/Server.Bll/Exceptions/AlreadyExistsException.cs +++ b/Server.Bll/Exceptions/AlreadyExistsException.cs @@ -1,4 +1,4 @@ -namespace Server.Exceptions.Register +namespace Server.Bll.Exceptions { public class AlreadyExistsException : UnknownReasonException { diff --git a/Server.Bll/Exceptions/InvalidCredentialsException.cs b/Server.Bll/Exceptions/InvalidCredentialsException.cs index 3cfba50..87e0d52 100644 --- a/Server.Bll/Exceptions/InvalidCredentialsException.cs +++ b/Server.Bll/Exceptions/InvalidCredentialsException.cs @@ -1,4 +1,4 @@ -namespace Server.Exceptions.LogIn +namespace Server.Bll.Exceptions { public class InvalidCredentialsException : LoginErrorException { diff --git a/Server.Bll/Exceptions/LoginCooldownException.cs b/Server.Bll/Exceptions/LoginCooldownException.cs index 6adc778..6114264 100644 --- a/Server.Bll/Exceptions/LoginCooldownException.cs +++ b/Server.Bll/Exceptions/LoginCooldownException.cs @@ -1,4 +1,4 @@ -namespace Server.Exceptions.LogIn +namespace Server.Bll.Exceptions { public class LoginCooldownException : LoginErrorException { diff --git a/Server.Bll/Exceptions/LoginErrorException.cs b/Server.Bll/Exceptions/LoginErrorException.cs index 2051446..a324d01 100644 --- a/Server.Bll/Exceptions/LoginErrorException.cs +++ b/Server.Bll/Exceptions/LoginErrorException.cs @@ -1,6 +1,6 @@ using System; -namespace Server.Exceptions.LogIn +namespace Server.Bll.Exceptions { public class LoginErrorException : Exception { diff --git a/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs b/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs index 180040a..94ca50e 100644 --- a/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs +++ b/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs @@ -1,6 +1,6 @@ using System; -namespace Server.GameLogic.Exceptions +namespace Server.Bll.Exceptions { public class TwinkGameRoomCreationException:Exception { diff --git a/Server.Bll/Exceptions/UnknownReasonException.cs b/Server.Bll/Exceptions/UnknownReasonException.cs index 1fc2609..0b9e075 100644 --- a/Server.Bll/Exceptions/UnknownReasonException.cs +++ b/Server.Bll/Exceptions/UnknownReasonException.cs @@ -1,6 +1,6 @@ using System; -namespace Server.Exceptions.Register +namespace Server.Bll.Exceptions { public class UnknownReasonException : Exception { diff --git a/Server.Bll/Exceptions/UserAlreadySignedInException.cs b/Server.Bll/Exceptions/UserAlreadySignedInException.cs index 3c859f0..71a0056 100644 --- a/Server.Bll/Exceptions/UserAlreadySignedInException.cs +++ b/Server.Bll/Exceptions/UserAlreadySignedInException.cs @@ -1,4 +1,4 @@ -namespace Server.Exceptions.LogIn +namespace Server.Bll.Exceptions { public class UserAlreadySignedInException : LoginErrorException { diff --git a/Server.Bll/Exceptions/UserNotFoundException.cs b/Server.Bll/Exceptions/UserNotFoundException.cs index f24f04e..2f5945c 100644 --- a/Server.Bll/Exceptions/UserNotFoundException.cs +++ b/Server.Bll/Exceptions/UserNotFoundException.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Server.Exceptions.LogIn +namespace Server.Bll.Exceptions { public class UserNotFoundException : LoginErrorException { diff --git a/Server.Bll/Models/AccountModel.cs b/Server.Bll/Models/AccountModel.cs index 533348d..d13d9b1 100644 --- a/Server.Bll/Models/AccountModel.cs +++ b/Server.Bll/Models/AccountModel.cs @@ -1,4 +1,4 @@ -namespace Server.Bll.Models +namespace Server.Bll.Models { public class AccountModel { diff --git a/Server.Bll/Services/AccountService.cs b/Server.Bll/Services/AccountService.cs deleted file mode 100644 index 48399a2..0000000 --- a/Server.Bll/Services/AccountService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; -using Server.Bll.Models; -using Server.Bll.Services.Interfaces; -using Server.Exceptions.LogIn; - -namespace Server.Bll.Services -{ - public class AccountService : IAccountService - { - private readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions()); - public Task RegisterAsync(AccountModel accountModel) - { - /*if (accountModel is null) - throw new InvalidCredentialsException(nameof(accountModel));*/ - throw new NotImplementedException(); - } - - public Task LogInAsync(AccountModel accountModel) - { - throw new System.NotImplementedException(); - } - - public Task LogOutAsync(string accountToken) - { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IAccountService.cs b/Server.Bll/Services/Interfaces/IAccountService.cs deleted file mode 100644 index 477eeaa..0000000 --- a/Server.Bll/Services/Interfaces/IAccountService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; -using Server.Bll.Models; - -namespace Server.Bll.Services.Interfaces -{ - public interface IAccountService - { - Task RegisterAsync(AccountModel accountModel); - Task LogInAsync(AccountModel accountModel); - Task LogOutAsync(string accountToken); - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index 850455a..452b132 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -3,7 +3,7 @@ namespace Server.Bll.Services.Interfaces { - public interface IRoundCoordinator + public interface IRoundService { Task CreateRoundAsync(); Task MakeMoveAsync(); diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index 1cc2c91..b6913b0 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -10,8 +10,8 @@ public class Account /// Id of account. Unique to everyone and similar with Statistics Id /// [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; init; } /// /// Nick name of Account @@ -26,7 +26,7 @@ public class Account /// /// Statistics id, connected to this account /// - public string StatisticsId { get; set; } + public int? StatisticsId { get; set; } /// /// Linked to this player statistics @@ -34,7 +34,7 @@ public class Account [ForeignKey("StatisticsId")] public virtual Statistics Statistics { get; set; } - public int RoomPlayerId { get; set; } + public int? RoomPlayerId { get; set; } [ForeignKey("RoomPlayerId")] public virtual RoomPlayers RoomPlayers { get; set; } diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index 1a87d63..7110717 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -11,13 +11,13 @@ public class Room /// Id of the room. Consists of 5 randomized chars /// [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string RoomId { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } /// /// Id of current round /// - public string RoundId { get; set; } + public int RoundId { get; set; } /// /// Round, linked to this room diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index a327bd3..8a92960 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -9,9 +9,10 @@ namespace Server.Dal.Entities public class RoomPlayers { [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } - public string RoomId { get; set; } + public int RoomId { get; set; } [ForeignKey("RoomId")] public virtual Room Room { get; set; } @@ -22,10 +23,9 @@ public class RoomPlayers public int SecondPlayerMove { get; set; } - public string RoundId { get; set; } + public int RoundId { get; set; } [ForeignKey("RoundId")] - [AllowNull] public virtual Round Round { get; set; } } } \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index e720c07..a32bc60 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -9,8 +8,8 @@ namespace Server.Dal.Entities public class Round { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; init; } public int RoomPlayersId { get; set; } @@ -19,14 +18,14 @@ public class Round public bool IsFinished { get; set; } - public DateTime TimeFinished { get; set; } + public DateTimeOffset TimeFinished { get; set; } - public string WinnerId { get; set; } + public int WinnerId { get; set; } [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } - public string LoserId { get; set; } + public int LoserId { get; set; } [ForeignKey("LoserId")] public virtual Account Loser { get; set; } diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index 0313c81..a1e31b0 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -11,13 +11,13 @@ public class Statistics /// [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } /// /// Id of linked account /// - public string AccountId { get; set; } + public int AccountId { get; set; } /// /// Linked account diff --git a/Server.Dal/IRepository.cs b/Server.Dal/IRepository.cs deleted file mode 100644 index 9519d04..0000000 --- a/Server.Dal/IRepository.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Server.Dal -{ - public interface IRepository : IDisposable - { - ValueTask GetById(string id); - Task> GetAll(); - TEntity Add(TEntity entity); - TEntity Remove(TEntity entity); - void Update(TEntity entity); - Task SaveAsync(); - void EnsureCreated(); - } -} \ No newline at end of file diff --git a/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs b/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs deleted file mode 100644 index 41804c9..0000000 --- a/Server.Dal/Migrations/20210509231534_InitialCommit.Designer.cs +++ /dev/null @@ -1,249 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210509231534_InitialCommit")] - partial class InitialCommit - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.5"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("StatisticsId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("CreationTime") - .HasColumnType("TEXT"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("RoomId"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinished") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayersId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany("Accounts") - .HasForeignKey("RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithMany() - .HasForeignKey("RoomId"); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("Room"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Navigation("Accounts"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs b/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs deleted file mode 100644 index 961a78a..0000000 --- a/Server.Dal/Migrations/20210510122522_FixEntities.Designer.cs +++ /dev/null @@ -1,265 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210510122522_FixEntities")] - partial class FixEntities - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.5"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("StatisticsId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("CreationTime") - .HasColumnType("TEXT"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("RoomId"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinished") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany("Accounts") - .HasForeignKey("RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithMany() - .HasForeignKey("RoomId"); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("Room"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Navigation("Accounts"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210510122522_FixEntities.cs b/Server.Dal/Migrations/20210510122522_FixEntities.cs deleted file mode 100644 index e9a55bd..0000000 --- a/Server.Dal/Migrations/20210510122522_FixEntities.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class FixEntities : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_Rounds_LoserId", - table: "Rounds", - column: "LoserId"); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_WinnerId", - table: "Rounds", - column: "WinnerId"); - - migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds", - column: "LoserId", - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds", - column: "WinnerId", - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds"); - - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds"); - - migrationBuilder.DropIndex( - name: "IX_Rounds_LoserId", - table: "Rounds"); - - migrationBuilder.DropIndex( - name: "IX_Rounds_WinnerId", - table: "Rounds"); - } - } -} diff --git a/Server.Dal/Migrations/20210514162359_PlayerMoves.cs b/Server.Dal/Migrations/20210514162359_PlayerMoves.cs deleted file mode 100644 index 4ee47e5..0000000 --- a/Server.Dal/Migrations/20210514162359_PlayerMoves.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class PlayerMoves : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FirstPlayerMove", - table: "RoomPlayers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "SecondPlayerMove", - table: "RoomPlayers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FirstPlayerMove", - table: "RoomPlayers"); - - migrationBuilder.DropColumn( - name: "SecondPlayerMove", - table: "RoomPlayers"); - } - } -} diff --git a/Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs b/Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs similarity index 75% rename from Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs rename to Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs index 42c4755..c6d84ab 100644 --- a/Server.Dal/Migrations/20210514162359_PlayerMoves.Designer.cs +++ b/Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs @@ -9,8 +9,8 @@ namespace Server.Dal.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20210514162359_PlayerMoves")] - partial class PlayerMoves + [Migration("20210711202013_InitialCommit")] + partial class InitialCommit { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -20,8 +20,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("Login") .HasColumnType("TEXT"); @@ -29,11 +30,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("RoomPlayerId") + b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("StatisticsId") - .HasColumnType("TEXT"); + b.Property("StatisticsId") + .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -46,8 +47,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Room", b => { - b.Property("RoomId") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("CreationTime") .HasColumnType("TEXT"); @@ -67,10 +69,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("RoundId") - .HasColumnType("TEXT"); + b.Property("RoundId") + .HasColumnType("INTEGER"); - b.HasKey("RoomId"); + b.HasKey("Id"); b.HasIndex("RoomPlayerId") .IsUnique(); @@ -89,11 +91,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("FirstPlayerMove") .HasColumnType("INTEGER"); - b.Property("RoomId") - .HasColumnType("TEXT"); + b.Property("RoomId") + .HasColumnType("INTEGER"); - b.Property("RoundId") - .HasColumnType("TEXT"); + b.Property("RoundId") + .HasColumnType("INTEGER"); b.Property("SecondPlayerMove") .HasColumnType("INTEGER"); @@ -109,23 +111,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Round", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LoserId") - .HasColumnType("TEXT"); + b.Property("LoserId") + .HasColumnType("INTEGER"); b.Property("RoomPlayersId") .HasColumnType("INTEGER"); - b.Property("TimeFinished") + b.Property("TimeFinished") .HasColumnType("TEXT"); - b.Property("WinnerId") - .HasColumnType("TEXT"); + b.Property("WinnerId") + .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -140,11 +143,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Statistics", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("AccountId") - .HasColumnType("TEXT"); + b.Property("AccountId") + .HasColumnType("INTEGER"); b.Property("Draws") .HasColumnType("INTEGER"); @@ -184,9 +188,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany("Accounts") - .HasForeignKey("RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoomPlayerId"); b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() @@ -207,7 +209,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId"); + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("RoomPlayers"); @@ -218,11 +222,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Room", "Room") .WithMany() - .HasForeignKey("RoomId"); + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId"); + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Room"); @@ -233,7 +241,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Account", "Loser") .WithMany() - .HasForeignKey("LoserId"); + .HasForeignKey("LoserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany() @@ -243,7 +253,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Account", "Winner") .WithMany() - .HasForeignKey("WinnerId"); + .HasForeignKey("WinnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Loser"); @@ -256,7 +268,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Account", "Account") .WithMany() - .HasForeignKey("AccountId"); + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Account"); }); diff --git a/Server.Dal/Migrations/20210509231534_InitialCommit.cs b/Server.Dal/Migrations/20210711202013_InitialCommit.cs similarity index 73% rename from Server.Dal/Migrations/20210509231534_InitialCommit.cs rename to Server.Dal/Migrations/20210711202013_InitialCommit.cs index d1d1b67..83c9d73 100644 --- a/Server.Dal/Migrations/20210509231534_InitialCommit.cs +++ b/Server.Dal/Migrations/20210711202013_InitialCommit.cs @@ -7,12 +7,30 @@ public partial class InitialCommit : Migration { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.CreateTable( + name: "Rounds", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomPlayersId = table.Column(type: "INTEGER", nullable: false), + IsFinished = table.Column(type: "INTEGER", nullable: false), + TimeFinished = table.Column(type: "TEXT", nullable: false), + WinnerId = table.Column(type: "INTEGER", nullable: false), + LoserId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rounds", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Statistics", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), - AccountId = table.Column(type: "TEXT", nullable: true), + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AccountId = table.Column(type: "INTEGER", nullable: false), Wins = table.Column(type: "INTEGER", nullable: true), Loss = table.Column(type: "INTEGER", nullable: true), Draws = table.Column(type: "INTEGER", nullable: true), @@ -32,11 +50,12 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Accounts", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), Login = table.Column(type: "TEXT", nullable: true), Password = table.Column(type: "TEXT", nullable: true), - StatisticsId = table.Column(type: "TEXT", nullable: true), - RoomPlayerId = table.Column(type: "INTEGER", nullable: false) + StatisticsId = table.Column(type: "INTEGER", nullable: true), + RoomPlayerId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { @@ -53,8 +72,9 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Rooms", columns: table => new { - RoomId = table.Column(type: "TEXT", nullable: false), - RoundId = table.Column(type: "TEXT", nullable: true), + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoundId = table.Column(type: "INTEGER", nullable: false), RoomPlayerId = table.Column(type: "INTEGER", nullable: false), IsPrivate = table.Column(type: "INTEGER", nullable: false), IsReady = table.Column(type: "INTEGER", nullable: false), @@ -64,23 +84,13 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_Rooms", x => x.RoomId); - }); - - migrationBuilder.CreateTable( - name: "Rounds", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - RoomPlayersId = table.Column(type: "INTEGER", nullable: false), - IsFinished = table.Column(type: "INTEGER", nullable: false), - TimeFinished = table.Column(type: "TEXT", nullable: false), - WinnerId = table.Column(type: "TEXT", nullable: true), - LoserId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Rounds", x => x.Id); + table.PrimaryKey("PK_Rooms", x => x.Id); + table.ForeignKey( + name: "FK_Rooms_Rounds_RoundId", + column: x => x.RoundId, + principalTable: "Rounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -89,8 +99,10 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - RoomId = table.Column(type: "TEXT", nullable: true), - RoundId = table.Column(type: "TEXT", nullable: true) + RoomId = table.Column(type: "INTEGER", nullable: false), + FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), + SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), + RoundId = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { @@ -99,14 +111,14 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_RoomPlayers_Rooms_RoomId", column: x => x.RoomId, principalTable: "Rooms", - principalColumn: "RoomId", - onDelete: ReferentialAction.Restrict); + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_RoomPlayers_Rounds_RoundId", column: x => x.RoundId, principalTable: "Rounds", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( @@ -140,52 +152,70 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Rooms", column: "RoundId"); + migrationBuilder.CreateIndex( + name: "IX_Rounds_LoserId", + table: "Rounds", + column: "LoserId"); + migrationBuilder.CreateIndex( name: "IX_Rounds_RoomPlayersId", table: "Rounds", column: "RoomPlayersId"); + migrationBuilder.CreateIndex( + name: "IX_Rounds_WinnerId", + table: "Rounds", + column: "WinnerId"); + migrationBuilder.CreateIndex( name: "IX_Statistics_AccountId", table: "Statistics", column: "AccountId"); migrationBuilder.AddForeignKey( - name: "FK_Statistics_Accounts_AccountId", - table: "Statistics", - column: "AccountId", + name: "FK_Rounds_Accounts_LoserId", + table: "Rounds", + column: "LoserId", principalTable: "Accounts", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); migrationBuilder.AddForeignKey( - name: "FK_Accounts_RoomPlayers_RoomPlayerId", - table: "Accounts", - column: "RoomPlayerId", - principalTable: "RoomPlayers", + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds", + column: "WinnerId", + principalTable: "Accounts", principalColumn: "Id", onDelete: ReferentialAction.Cascade); migrationBuilder.AddForeignKey( - name: "FK_Rooms_RoomPlayers_RoomPlayerId", - table: "Rooms", - column: "RoomPlayerId", + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId", principalTable: "RoomPlayers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); migrationBuilder.AddForeignKey( - name: "FK_Rooms_Rounds_RoundId", - table: "Rooms", - column: "RoundId", - principalTable: "Rounds", + name: "FK_Statistics_Accounts_AccountId", + table: "Statistics", + column: "AccountId", + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Accounts_RoomPlayers_RoomPlayerId", + table: "Accounts", + column: "RoomPlayerId", + principalTable: "RoomPlayers", principalColumn: "Id", onDelete: ReferentialAction.Restrict); migrationBuilder.AddForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId", + name: "FK_Rooms_RoomPlayers_RoomPlayerId", + table: "Rooms", + column: "RoomPlayerId", principalTable: "RoomPlayers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index 047371f..cb7e21b 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -18,8 +18,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("Login") .HasColumnType("TEXT"); @@ -27,11 +28,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("RoomPlayerId") + b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("StatisticsId") - .HasColumnType("TEXT"); + b.Property("StatisticsId") + .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -44,8 +45,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Room", b => { - b.Property("RoomId") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("CreationTime") .HasColumnType("TEXT"); @@ -65,10 +67,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("RoundId") - .HasColumnType("TEXT"); + b.Property("RoundId") + .HasColumnType("INTEGER"); - b.HasKey("RoomId"); + b.HasKey("Id"); b.HasIndex("RoomPlayerId") .IsUnique(); @@ -87,11 +89,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FirstPlayerMove") .HasColumnType("INTEGER"); - b.Property("RoomId") - .HasColumnType("TEXT"); + b.Property("RoomId") + .HasColumnType("INTEGER"); - b.Property("RoundId") - .HasColumnType("TEXT"); + b.Property("RoundId") + .HasColumnType("INTEGER"); b.Property("SecondPlayerMove") .HasColumnType("INTEGER"); @@ -107,23 +109,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Round", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LoserId") - .HasColumnType("TEXT"); + b.Property("LoserId") + .HasColumnType("INTEGER"); b.Property("RoomPlayersId") .HasColumnType("INTEGER"); - b.Property("TimeFinished") + b.Property("TimeFinished") .HasColumnType("TEXT"); - b.Property("WinnerId") - .HasColumnType("TEXT"); + b.Property("WinnerId") + .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -138,11 +141,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Statistics", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("AccountId") - .HasColumnType("TEXT"); + b.Property("AccountId") + .HasColumnType("INTEGER"); b.Property("Draws") .HasColumnType("INTEGER"); @@ -182,9 +186,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany("Accounts") - .HasForeignKey("RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoomPlayerId"); b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() @@ -205,7 +207,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId"); + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("RoomPlayers"); @@ -216,11 +220,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Room", "Room") .WithMany() - .HasForeignKey("RoomId"); + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId"); + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Room"); @@ -231,7 +239,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Account", "Loser") .WithMany() - .HasForeignKey("LoserId"); + .HasForeignKey("LoserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany() @@ -241,7 +251,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Account", "Winner") .WithMany() - .HasForeignKey("WinnerId"); + .HasForeignKey("WinnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Loser"); @@ -254,7 +266,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Account", "Account") .WithMany() - .HasForeignKey("AccountId"); + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Account"); }); diff --git a/Server.Dal/Repository.cs b/Server.Dal/Repository.cs deleted file mode 100644 index 373948f..0000000 --- a/Server.Dal/Repository.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Server.Dal.Context; - -namespace Server.Dal -{ - public class Repository :IRepository where TEntity: class, new() - { - private bool _disposed; - private ServerContext ServerContext { get;} - - public Repository(ServerContext serverContext) - { - ServerContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); - } - public virtual async void Dispose() - { - await Dispose(true); - GC.SuppressFinalize(this); - } - - public async ValueTask GetById(string id) - { - return await ServerContext.FindAsync(id); - } - - public async Task> GetAll() - { - return await Task.FromResult(ServerContext.Set().AsQueryable()); - } - - public TEntity Add(TEntity entity) - { - var addEntityTask = ServerContext.AddAsync(entity); - - return addEntityTask.IsCompletedSuccessfully - ? addEntityTask.Result.Entity : null; - } - - public TEntity Remove(TEntity entity) - { - return ServerContext.Remove(entity).Entity; - } - - public void Update(TEntity entity) - { - ServerContext.Entry(entity).State = EntityState.Modified; - } - - public async Task SaveAsync() - { - return await ServerContext.SaveChangesAsync(); - } - - public void EnsureCreated() - { - throw new System.NotImplementedException(); - } - - private async Task Dispose(bool dispose) - { - if (!dispose || _disposed) - return; - _disposed = true; - await ServerContext.DisposeAsync(); - } - } -} \ No newline at end of file diff --git a/Server/Account b/Server.Host/Account similarity index 100% rename from Server/Account rename to Server.Host/Account diff --git a/Server/Contracts/AccountDto.cs b/Server.Host/Contracts/AccountDto.cs similarity index 79% rename from Server/Contracts/AccountDto.cs rename to Server.Host/Contracts/AccountDto.cs index c467efb..81e7c31 100644 --- a/Server/Contracts/AccountDto.cs +++ b/Server.Host/Contracts/AccountDto.cs @@ -9,7 +9,6 @@ public record AccountDto public string Login { get; set; } [Required(ErrorMessage = "Password is required!!")] [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] - public string Password { get; set;} - public DateTime LastRequest { get; set;} + public string Password { get; set;} } } \ No newline at end of file diff --git a/Server/Contracts/Requests/LoginRequest.cs b/Server.Host/Contracts/Requests/LoginRequest.cs similarity index 100% rename from Server/Contracts/Requests/LoginRequest.cs rename to Server.Host/Contracts/Requests/LoginRequest.cs diff --git a/Server/Contracts/Requests/RegisterRequest.cs b/Server.Host/Contracts/Requests/RegisterRequest.cs similarity index 77% rename from Server/Contracts/Requests/RegisterRequest.cs rename to Server.Host/Contracts/Requests/RegisterRequest.cs index 3d6cb35..b6ef61a 100644 --- a/Server/Contracts/Requests/RegisterRequest.cs +++ b/Server.Host/Contracts/Requests/RegisterRequest.cs @@ -8,6 +8,7 @@ public class RegisterRequest public string Login { get; set; } [Required(ErrorMessage = "Password is required!")] + [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length. Must be 6-20")] public string Password { get; set; } } } \ No newline at end of file diff --git a/Server/Contracts/StatisticsDto.cs b/Server.Host/Contracts/StatisticsDto.cs similarity index 100% rename from Server/Contracts/StatisticsDto.cs rename to Server.Host/Contracts/StatisticsDto.cs diff --git a/Server/Contracts/ViewModels/AccountViewModel.cs b/Server.Host/Contracts/ViewModels/AccountViewModel.cs similarity index 100% rename from Server/Contracts/ViewModels/AccountViewModel.cs rename to Server.Host/Contracts/ViewModels/AccountViewModel.cs diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs new file mode 100644 index 0000000..b894533 --- /dev/null +++ b/Server.Host/Controllers/AccountController.cs @@ -0,0 +1,61 @@ +using System.Net; +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Server.Authentication.Services; +using Server.Contracts; +using Server.Contracts.Requests; + +namespace Server.Controllers +{ + [ApiController] + [Route ("[controller]/[action]")] + [Consumes(MediaTypeNames.Application.Json)] + [Produces(MediaTypeNames.Application.Json)] + public class AccountController : ControllerBase + { + private readonly IAuthService _authService; + + public AccountController(IAuthService authService) + { + _authService = authService; + } + [HttpPost] + [AllowAnonymous] + [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task Register(RegisterRequest registerRequest) + { + if (!ModelState.IsValid) return BadRequest(); + var newAccount = await _authService + .RegisterAsync(registerRequest.Login,registerRequest.Password); + return newAccount.Match( + integer => Ok(integer), + exception => BadRequest(exception)); + } + + [HttpPost] + [AllowAnonymous] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task Login(AccountDto accountDto) + { + if (!ModelState.IsValid) return BadRequest(); + var newAccount = + await _authService.LoginAsync(accountDto.Login, accountDto.Password); + + return newAccount.Match( + Ok, + userException => BadRequest(userException) + ); + } + + [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] + public ActionResult Logout(string sessionId) + { + return Ok(sessionId); + } + } +} \ No newline at end of file diff --git a/Server/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs similarity index 86% rename from Server/Controllers/RoomController.cs rename to Server.Host/Controllers/RoomController.cs index a642a34..504bacb 100644 --- a/Server/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -1,14 +1,17 @@ -using Microsoft.AspNetCore.Mvc; -using System; +using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace Server.Controllers { [ApiController] [Route("[controller]/[action]")] [Produces(MediaTypeNames.Application.Json)] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RoomController : ControllerBase { diff --git a/Server/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs similarity index 96% rename from Server/Controllers/RoundController.cs rename to Server.Host/Controllers/RoundController.cs index b068991..e93cd6c 100644 --- a/Server/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -1,9 +1,9 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; +using System; using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Server.Controllers { diff --git a/Server/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs similarity index 100% rename from Server/Controllers/StatisticsController.cs rename to Server.Host/Controllers/StatisticsController.cs diff --git a/Server/Extensions/DatabaseExtension.cs b/Server.Host/Extensions/DatabaseExtension.cs similarity index 71% rename from Server/Extensions/DatabaseExtension.cs rename to Server.Host/Extensions/DatabaseExtension.cs index 097ab72..120bd33 100644 --- a/Server/Extensions/DatabaseExtension.cs +++ b/Server.Host/Extensions/DatabaseExtension.cs @@ -12,9 +12,10 @@ public static class DatabaseExtension public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { return service.AddDbContext( - builder => builder.UseSqlite - (configuration.GetConnectionString(DatabaseConnection), - x=> x.MigrationsAssembly(MigrationAssemblyName))); + builder => builder.UseSqlite + (configuration.GetConnectionString(DatabaseConnection), + x => x.MigrationsAssembly(MigrationAssemblyName)), + ServiceLifetime.Transient); } } } \ No newline at end of file diff --git a/Server/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs similarity index 99% rename from Server/Extensions/LoggingMiddleware.cs rename to Server.Host/Extensions/LoggingMiddleware.cs index d19c2dd..70827ff 100644 --- a/Server/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Server.Extensions +namespace Server.Host.Extensions { internal class LoggingMiddleware { diff --git a/Server/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs similarity index 55% rename from Server/Extensions/ServiceExtension.cs rename to Server.Host/Extensions/ServiceExtension.cs index 72d3f73..2d370b1 100644 --- a/Server/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -1,17 +1,13 @@ using System; using Microsoft.Extensions.DependencyInjection; -using Server.Bll.Services; -using Server.Bll.Services.Interfaces; - -namespace Server.Extensions +namespace Server.Host.Extensions { public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - return service. - AddSingleton(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs new file mode 100644 index 0000000..afa1f5d --- /dev/null +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace Server.Host.Extensions +{ + /// + /// Swagger extension + /// + public static class SwaggerExtension + { + /// + /// Registers swagger. + /// + /// Service collection. + /// Service collection. + public static IServiceCollection AddSwagger(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + + services.AddSwaggerGen(options => + { + //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + //options.IncludeXmlComments(xmlPath); + options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); + + options.AddSecurityRequirement( + new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = "Bearer", + Type = ReferenceType.SecurityScheme + }, + }, + Array.Empty() + } + }); + + options.AddSecurityDefinition( + "Bearer", + new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Scheme = "Bearer", + Name = "Authorization", + Description = "JWT token", + BearerFormat = "JWT" + }); + }); + + return services; + } + } +} \ No newline at end of file diff --git a/Server/Program.cs b/Server.Host/Program.cs similarity index 90% rename from Server/Program.cs rename to Server.Host/Program.cs index bb16d90..b3f13ed 100644 --- a/Server/Program.cs +++ b/Server.Host/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; +using Server.Host; namespace Server { @@ -13,14 +14,13 @@ public static void Main(string[] args) } private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureLogging(loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.SetMinimumLevel(LogLevel.Information); loggingBuilder.AddSerilog(new LoggerConfiguration() .WriteTo.Console() - .WriteTo.File("app.log") .CreateLogger()); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); diff --git a/Server/Properties/launchSettings.json b/Server.Host/Properties/launchSettings.json similarity index 100% rename from Server/Properties/launchSettings.json rename to Server.Host/Properties/launchSettings.json diff --git a/Server/Server.csproj b/Server.Host/Server.Host.csproj similarity index 90% rename from Server/Server.csproj rename to Server.Host/Server.Host.csproj index b5255b5..7a81264 100644 --- a/Server/Server.csproj +++ b/Server.Host/Server.Host.csproj @@ -2,6 +2,7 @@ net5.0 + Server @@ -24,6 +25,7 @@ + diff --git a/Server/Startup.cs b/Server.Host/Startup.cs similarity index 82% rename from Server/Startup.cs rename to Server.Host/Startup.cs index f3dec2f..d28a739 100644 --- a/Server/Startup.cs +++ b/Server.Host/Startup.cs @@ -4,9 +4,10 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; +using Server.Authentication.Extensions; using Server.Dal.Context; using Server.Extensions; +using Server.Host.Extensions; namespace Server { @@ -21,22 +22,25 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { - services.AddServices() + services //.AddServices() .AddDatabase(Configuration) - .AddSwagger(); + .AddSwagger() + .AddAuthentications(); services.AddControllers(); services.AddCors(); } - public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, + ServerContext serverContext) { + serverContext?.Database.Migrate(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server.Host v1")); } app.UseCors(builder => builder.AllowAnyOrigin() diff --git a/Server/appsettings.Development.json b/Server.Host/appsettings.Development.json similarity index 100% rename from Server/appsettings.Development.json rename to Server.Host/appsettings.Development.json diff --git a/Server/appsettings.json b/Server.Host/appsettings.json similarity index 67% rename from Server/appsettings.json rename to Server.Host/appsettings.json index c54b648..e163015 100644 --- a/Server/appsettings.json +++ b/Server.Host/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DatabaseConnection": "Host=localhost;Port=5433;Database=sqlite;Username=sqlite;Password=sqlite" + "DatabaseConnection": "Data Source=database.db" }, "Logging": { "LogLevel": { diff --git a/Server/Controllers/AccountController.cs b/Server/Controllers/AccountController.cs deleted file mode 100644 index fc349d3..0000000 --- a/Server/Controllers/AccountController.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Net.Mime; -using System.Threading.Tasks; -using Mapster; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Server.Bll.Models; -using Server.Bll.Services.Interfaces; -using Server.Contracts; -using Server.Contracts.Requests; -using Server.Contracts.ViewModels; - -namespace Server.Controllers -{ - [ApiController] - [Route ("[controller]/[action]")] - [Consumes(MediaTypeNames.Application.Json)] - [Produces(MediaTypeNames.Application.Json)] - public class AccountController : ControllerBase - { - private readonly ILogger _logger; - private readonly IAccountService _accountService; - - public AccountController( - ILogger logger, - IAccountService accountService) - { - _logger = logger; - _accountService = accountService; - } - [HttpPost] - [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task Register(RegisterRequest registerRequest) - { - try - { - var result = _accountService.RegisterAsync(registerRequest.Adapt()); - return Ok(await result); - } - catch (Exception exception) - { - return BadRequest(); - } - } - - [HttpPost] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task> Login(AccountDto accountDto) - { - throw new NotImplementedException(); - } - - - - [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] - public async Task> Logout(string sessionId) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Server/Extensions/SwaggerExtension.cs b/Server/Extensions/SwaggerExtension.cs deleted file mode 100644 index c90d19a..0000000 --- a/Server/Extensions/SwaggerExtension.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; - -namespace Server.Extensions -{ - public static class SwaggerExtension - { - //todo: add here documentation - public static IServiceCollection AddSwagger(this IServiceCollection service) - { - return service.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); - }); - } - } -} \ No newline at end of file diff --git a/Server/Statistics b/Server/Statistics deleted file mode 100644 index c80bf6a..0000000 --- a/Server/Statistics +++ /dev/null @@ -1 +0,0 @@ -{"1a047fbd-901f-464f-ab6b-e6d0f46f1558":{"Id":"1a047fbd-901f-464f-ab6b-e6d0f46f1558","Login":"string","Wins":0,"Loss":0,"Draws":0,"WinLossRatio":0.0,"TimeSpent":null,"UsedRock":0,"UsedPaper":0,"UsedScissors":0,"Score":0},"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Wins":0,"Loss":0,"Draws":0,"WinLossRatio":0.0,"TimeSpent":null,"UsedRock":0,"UsedPaper":0,"UsedScissors":0,"Score":0}} \ No newline at end of file From 7d8dae6f9884ae4676c2eaf9ed1d073d345bc824 Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Sun, 11 Jul 2021 23:30:42 +0300 Subject: [PATCH 12/56] Updated versions --- Client/Client.csproj | 4 ++-- Server.Host/Server.Host.csproj | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Client/Client.csproj b/Client/Client.csproj index 9140622..591893e 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 7a81264..d98634c 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -6,17 +6,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + From e0b744d94de6b76f6602f2d5ccbd1fc1f8e06b8b Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Tue, 13 Jul 2021 17:32:00 +0300 Subject: [PATCH 13/56] Deleted exceptions. --- .../Services/IAuthService.cs | 1 - .../Exceptions/AlreadyExistsException.cs | 15 ------ Server.Bll/Exceptions/ExceptionTemplates.cs | 9 ++++ .../Exceptions/InvalidCredentialsException.cs | 16 ------ .../Exceptions/LoginCooldownException.cs | 16 ------ Server.Bll/Exceptions/LoginErrorException.cs | 24 --------- Server.Bll/Exceptions/RoomException.cs | 14 ++++++ .../TwinkGameRoomCreationException.cs | 12 ----- .../Exceptions/UnknownReasonException.cs | 24 --------- .../UserAlreadySignedInException.cs | 15 ------ .../Exceptions/UserNotFoundException.cs | 13 ----- Server.Bll/Server.Bll.csproj | 1 + Server.Bll/Services/RoomService.cs | 50 +++++++++++++++++++ Server.Host/Controllers/AccountController.cs | 1 - 14 files changed, 74 insertions(+), 137 deletions(-) delete mode 100644 Server.Bll/Exceptions/AlreadyExistsException.cs create mode 100644 Server.Bll/Exceptions/ExceptionTemplates.cs delete mode 100644 Server.Bll/Exceptions/InvalidCredentialsException.cs delete mode 100644 Server.Bll/Exceptions/LoginCooldownException.cs delete mode 100644 Server.Bll/Exceptions/LoginErrorException.cs create mode 100644 Server.Bll/Exceptions/RoomException.cs delete mode 100644 Server.Bll/Exceptions/TwinkGameRoomCreationException.cs delete mode 100644 Server.Bll/Exceptions/UnknownReasonException.cs delete mode 100644 Server.Bll/Exceptions/UserAlreadySignedInException.cs delete mode 100644 Server.Bll/Exceptions/UserNotFoundException.cs create mode 100644 Server.Bll/Services/RoomService.cs diff --git a/Server.Authentication/Services/IAuthService.cs b/Server.Authentication/Services/IAuthService.cs index 4372fb7..8cde3c2 100644 --- a/Server.Authentication/Services/IAuthService.cs +++ b/Server.Authentication/Services/IAuthService.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using OneOf; using Server.Authentication.Exceptions; diff --git a/Server.Bll/Exceptions/AlreadyExistsException.cs b/Server.Bll/Exceptions/AlreadyExistsException.cs deleted file mode 100644 index 62409a7..0000000 --- a/Server.Bll/Exceptions/AlreadyExistsException.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public class AlreadyExistsException : UnknownReasonException - { - public AlreadyExistsException() - { - - } - - public AlreadyExistsException(string message) : base(string.Format($"This account already exists")) - { - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs new file mode 100644 index 0000000..ae387d0 --- /dev/null +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -0,0 +1,9 @@ +namespace Server.Bll.Exceptions +{ + public static class ExceptionTemplates + { + public const string Unknown = "Unknown error occured. Please try again"; + public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; + + } +} \ No newline at end of file diff --git a/Server.Bll/Exceptions/InvalidCredentialsException.cs b/Server.Bll/Exceptions/InvalidCredentialsException.cs deleted file mode 100644 index 87e0d52..0000000 --- a/Server.Bll/Exceptions/InvalidCredentialsException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public class InvalidCredentialsException : LoginErrorException - { - protected InvalidCredentialsException() - { - - } - - public InvalidCredentialsException(string message) : - base($"Invalid username of password!") - { - } - - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/LoginCooldownException.cs b/Server.Bll/Exceptions/LoginCooldownException.cs deleted file mode 100644 index 6114264..0000000 --- a/Server.Bll/Exceptions/LoginCooldownException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public class LoginCooldownException : LoginErrorException - { - protected LoginCooldownException() - { - - } - - public LoginCooldownException(string message, int afterTime) : - base($"You got a cooldown! Try in {afterTime} seconds") - { - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/LoginErrorException.cs b/Server.Bll/Exceptions/LoginErrorException.cs deleted file mode 100644 index a324d01..0000000 --- a/Server.Bll/Exceptions/LoginErrorException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Server.Bll.Exceptions -{ - public class LoginErrorException : Exception - { - protected LoginErrorException() - { - - } - - protected LoginErrorException(string message) : - base(message) - { - - } - - public LoginErrorException(string message, Exception inner) - : base(message, inner) - { - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/RoomException.cs b/Server.Bll/Exceptions/RoomException.cs new file mode 100644 index 0000000..363485f --- /dev/null +++ b/Server.Bll/Exceptions/RoomException.cs @@ -0,0 +1,14 @@ +namespace Server.Bll.Exceptions +{ + public readonly struct RoomException + { + public int Code { get;} + public string Message { get; } + + public RoomException(string message, int code, string template) + { + Message = string.Format(template,message); + Code = code; + } + } +} \ No newline at end of file diff --git a/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs b/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs deleted file mode 100644 index 94ca50e..0000000 --- a/Server.Bll/Exceptions/TwinkGameRoomCreationException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Server.Bll.Exceptions -{ - public class TwinkGameRoomCreationException:Exception - { - public TwinkGameRoomCreationException() : - base($"Failed to creaate one more game when you are sitting in another room!") - { - } - } -} diff --git a/Server.Bll/Exceptions/UnknownReasonException.cs b/Server.Bll/Exceptions/UnknownReasonException.cs deleted file mode 100644 index 0b9e075..0000000 --- a/Server.Bll/Exceptions/UnknownReasonException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Server.Bll.Exceptions -{ - public class UnknownReasonException : Exception - { - public UnknownReasonException() - { - - } - - public UnknownReasonException(string message) : - base(message) - { - - } - - public UnknownReasonException(string message, Exception inner) : - base(message, inner) - { - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/UserAlreadySignedInException.cs b/Server.Bll/Exceptions/UserAlreadySignedInException.cs deleted file mode 100644 index 71a0056..0000000 --- a/Server.Bll/Exceptions/UserAlreadySignedInException.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public class UserAlreadySignedInException : LoginErrorException - { - public UserAlreadySignedInException() - { - - } - - public UserAlreadySignedInException(string message) : base(string.Format($"Already signed in!")) - { - - } - } -} \ No newline at end of file diff --git a/Server.Bll/Exceptions/UserNotFoundException.cs b/Server.Bll/Exceptions/UserNotFoundException.cs deleted file mode 100644 index 2f5945c..0000000 --- a/Server.Bll/Exceptions/UserNotFoundException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public class UserNotFoundException : LoginErrorException - { - public UserNotFoundException() - { - } - public UserNotFoundException(string message) : - base($"This user is not found!") - { - } - } -} diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index c467c6d..6ce499a 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -7,6 +7,7 @@ + diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs new file mode 100644 index 0000000..78f0043 --- /dev/null +++ b/Server.Bll/Services/RoomService.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using OneOf; +using Server.Bll.Exceptions; +using Server.Bll.Models; +using Server.Dal.Context; +using Server.Dal.Entities; + +namespace Server.Bll.Services +{ + public class RoomService : IRoomService + { + private readonly DbSet _rooms; + private readonly ServerContext _repository; + + public RoomService(ServerContext repository) + { + _repository = repository; + _rooms = _repository.Rooms; + } + + public async Task> CreateRoom(int userId) + { + throw new System.NotImplementedException(); + } + + public async Task> GetRoom() + { + throw new System.NotImplementedException(); + } + + public async Task UpdateRoom() + { + throw new System.NotImplementedException(); + } + + public async Task DeleteRoom() + { + throw new System.NotImplementedException(); + } + } + + public interface IRoomService + { + Task> CreateRoom(int userId); + Task> GetRoom(); + Task UpdateRoom(); + Task DeleteRoom(); + } +} \ No newline at end of file diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index b894533..ce874e8 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -36,7 +36,6 @@ public async Task Register(RegisterRequest registerRequest) } [HttpPost] - [AllowAnonymous] [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] public async Task Login(AccountDto accountDto) From a1f2baec81eca1bed5c18841e6f386ca67d30c73 Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Tue, 13 Jul 2021 20:02:21 +0300 Subject: [PATCH 14/56] IMplemented Rooms logic (almost) --- .../Models/ApplicationUser.cs | 36 +++ Server.Authentication/Models/AuthUser.cs | 1 + .../Models/Interfaces/IApplicationUser.cs | 13 + .../Models/{ => Interfaces}/IAuthUser.cs | 2 +- Server.Authentication/Services/AuthService.cs | 1 - Server.Bll/Exceptions/ExceptionTemplates.cs | 4 +- Server.Bll/Exceptions/RoomException.cs | 4 +- Server.Bll/Mapper/RoomMapper.cs | 59 +++++ Server.Bll/Models/AccountModel.cs | 3 +- Server.Bll/Models/RoomModel.cs | 8 +- Server.Bll/Models/RoomPlayersModel.cs | 14 +- Server.Bll/Models/RoundModel.cs | 2 +- Server.Bll/Server.Bll.csproj | 2 +- .../Services/Interfaces/IRoomService.cs | 17 +- Server.Bll/Services/RockPaperScissors.cs | 8 - Server.Bll/Services/RoomService.cs | 121 ++++++++-- Server.Dal/Context/ServerContext.cs | 27 +++ Server.Dal/Entities/Account.cs | 9 +- Server.Dal/Entities/Room.cs | 11 +- Server.Dal/Entities/RoomPlayers.cs | 15 +- Server.Dal/Entities/Round.cs | 4 +- ...s => 20210713165841_NULLABLES.Designer.cs} | 88 ++++--- ...lCommit.cs => 20210713165841_NULLABLES.cs} | 226 ++++++++++-------- .../Migrations/ServerContextModelSnapshot.cs | 84 ++++--- Server.Host/Controllers/RoomController.cs | 46 +++- Server.Host/Extensions/LoggingMiddleware.cs | 2 +- Server.Host/Extensions/ServiceExtension.cs | 15 +- Server.Host/Startup.cs | 3 + 28 files changed, 566 insertions(+), 259 deletions(-) create mode 100644 Server.Authentication/Models/ApplicationUser.cs create mode 100644 Server.Authentication/Models/Interfaces/IApplicationUser.cs rename Server.Authentication/Models/{ => Interfaces}/IAuthUser.cs (82%) create mode 100644 Server.Bll/Mapper/RoomMapper.cs delete mode 100644 Server.Bll/Services/RockPaperScissors.cs rename Server.Dal/Migrations/{20210711202013_InitialCommit.Designer.cs => 20210713165841_NULLABLES.Designer.cs} (79%) rename Server.Dal/Migrations/{20210711202013_InitialCommit.cs => 20210713165841_NULLABLES.cs} (75%) diff --git a/Server.Authentication/Models/ApplicationUser.cs b/Server.Authentication/Models/ApplicationUser.cs new file mode 100644 index 0000000..3150967 --- /dev/null +++ b/Server.Authentication/Models/ApplicationUser.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Microsoft.AspNetCore.Http; +using Server.Authentication.Models.Interfaces; + +namespace Server.Authentication.Models +{ + /// + /// Application user + /// + public class ApplicationUser: IApplicationUser + { + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// Constructor + /// + /// http accessor + public ApplicationUser(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// This user id + /// + public int Id => GetUserId(); + + private int GetUserId() + { + var request = _httpContextAccessor.HttpContext + ?.User.Claims.FirstOrDefault(x => x.Type == "id"); + + return int.TryParse(request?.Value, out var id) ? id : 0; + } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/AuthUser.cs b/Server.Authentication/Models/AuthUser.cs index 412828d..7c15b50 100644 --- a/Server.Authentication/Models/AuthUser.cs +++ b/Server.Authentication/Models/AuthUser.cs @@ -1,5 +1,6 @@ using System.Linq; using Microsoft.AspNetCore.Http; +using Server.Authentication.Models.Interfaces; namespace Server.Authentication.Models { diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs new file mode 100644 index 0000000..66248be --- /dev/null +++ b/Server.Authentication/Models/Interfaces/IApplicationUser.cs @@ -0,0 +1,13 @@ +namespace Server.Authentication.Models.Interfaces +{ + /// + /// Interface for ApplicationUser class + /// + public interface IApplicationUser + { + /// + /// User request id from token + /// + int Id { get; } + } +} \ No newline at end of file diff --git a/Server.Authentication/Models/IAuthUser.cs b/Server.Authentication/Models/Interfaces/IAuthUser.cs similarity index 82% rename from Server.Authentication/Models/IAuthUser.cs rename to Server.Authentication/Models/Interfaces/IAuthUser.cs index f3e4566..3e65afb 100644 --- a/Server.Authentication/Models/IAuthUser.cs +++ b/Server.Authentication/Models/Interfaces/IAuthUser.cs @@ -1,4 +1,4 @@ -namespace Server.Authentication.Models +namespace Server.Authentication.Models.Interfaces { /// /// Interface for ApplicationUser class diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 64531bf..f4a3793 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -64,7 +64,6 @@ public async Task> await _repository.SaveChangesAsync(); - account = await _accounts.FirstOrDefaultAsync(x => x.Login == login); var accountStatistics = new Statistics { AccountId = account.Id diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index ae387d0..6abf800 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -4,6 +4,8 @@ public static class ExceptionTemplates { public const string Unknown = "Unknown error occured. Please try again"; public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; - + public const string RoomNotExists = "Room does not exist."; + public const string RoomFull = "This room is full."; + } } \ No newline at end of file diff --git a/Server.Bll/Exceptions/RoomException.cs b/Server.Bll/Exceptions/RoomException.cs index 363485f..825098b 100644 --- a/Server.Bll/Exceptions/RoomException.cs +++ b/Server.Bll/Exceptions/RoomException.cs @@ -5,9 +5,9 @@ public readonly struct RoomException public int Code { get;} public string Message { get; } - public RoomException(string message, int code, string template) + public RoomException(string template, int code) { - Message = string.Format(template,message); + Message = template; Code = code; } } diff --git a/Server.Bll/Mapper/RoomMapper.cs b/Server.Bll/Mapper/RoomMapper.cs new file mode 100644 index 0000000..ce6fecf --- /dev/null +++ b/Server.Bll/Mapper/RoomMapper.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Server.Bll.Models; +using Server.Dal.Entities; + +namespace Server.Bll.Mapper +{ + public static class RoomMapper + { + public static RoomModel ToRoomModel(this Room room) + { + var roomPlayersModel = new RoomPlayersModel(); + var firstPlayerModel = new AccountModel(); + var secondPlayerModel = new AccountModel(); + if(room == null) + return null; + if (room.RoomPlayers == null) + return new RoomModel + { + Id = room.Id, + RoundId = room.RoundId, + RoomCode = room.RoomCode, + RoomPlayers = roomPlayersModel, + IsPrivate = room.IsPrivate, + IsReady = room.IsReady, + IsFull = room.IsFull, + CreationTime = room.CreationTime, + IsRoundEnded = room.IsRoundEnded, + }; + if (room.RoomPlayers.FirstPlayer != null) + { + firstPlayerModel.Login = room.RoomPlayers.FirstPlayer.Login; + } + if (room.RoomPlayers.SecondPlayer != null) + { + firstPlayerModel.Login = room.RoomPlayers.SecondPlayer.Login; + } + roomPlayersModel = new RoomPlayersModel + { + FirstPlayer = firstPlayerModel, + SecondPlayer = secondPlayerModel, + FirstPlayerMove = room.RoomPlayers.FirstPlayerMove, + SecondPlayerMove = room.RoomPlayers.SecondPlayerMove + }; + + return new RoomModel + { + Id = room.Id, + RoundId = room.RoundId, + RoomCode = room.RoomCode, + RoomPlayers = roomPlayersModel, + IsPrivate = room.IsPrivate, + IsReady = room.IsReady, + IsFull = room.IsFull, + CreationTime = room.CreationTime, + IsRoundEnded = room.IsRoundEnded, + }; + } + } +} \ No newline at end of file diff --git a/Server.Bll/Models/AccountModel.cs b/Server.Bll/Models/AccountModel.cs index d13d9b1..a8e5e12 100644 --- a/Server.Bll/Models/AccountModel.cs +++ b/Server.Bll/Models/AccountModel.cs @@ -1,9 +1,8 @@ + namespace Server.Bll.Models { public class AccountModel { public string Login { get; set; } - - public string Password { get; set; } } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs index 9c22c62..3b28746 100644 --- a/Server.Bll/Models/RoomModel.cs +++ b/Server.Bll/Models/RoomModel.cs @@ -1,10 +1,14 @@ using System; +using Mapster; namespace Server.Bll.Models { public class RoomModel { - public RoundModel Round { get; set; } + public int Id { get; set; } + public int? RoundId { get; set; } + + public string RoomCode { get; set; } public RoomPlayersModel RoomPlayers { get; set; } @@ -14,7 +18,7 @@ public class RoomModel public bool IsFull { get; set; } - public DateTime CreationTime { get; set; } + public DateTimeOffset CreationTime { get; set; } public bool IsRoundEnded { get; set; } } diff --git a/Server.Bll/Models/RoomPlayersModel.cs b/Server.Bll/Models/RoomPlayersModel.cs index 74efb40..bf82c30 100644 --- a/Server.Bll/Models/RoomPlayersModel.cs +++ b/Server.Bll/Models/RoomPlayersModel.cs @@ -1,17 +1,11 @@ -using System.Collections.Generic; - + namespace Server.Bll.Models { public class RoomPlayersModel - { - public RoomModel Room { get; set; } - - public ICollection Accounts { get; set; } - + { + public AccountModel FirstPlayer { get; set; } public int FirstPlayerMove { get; set; } - + public AccountModel SecondPlayer { get; set; } public int SecondPlayerMove { get; set; } - - public RoundModel Round { get; set; } } } \ No newline at end of file diff --git a/Server.Bll/Models/RoundModel.cs b/Server.Bll/Models/RoundModel.cs index 5d0af48..1de5bf3 100644 --- a/Server.Bll/Models/RoundModel.cs +++ b/Server.Bll/Models/RoundModel.cs @@ -8,7 +8,7 @@ public class RoundModel public bool IsFinished { get; set; } - public DateTime TimeFinished { get; set; } + public DateTimeOffset TimeFinished { get; set; } public AccountModel Winner { get; set; } diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 6ce499a..0aee6ab 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -13,5 +13,5 @@ - + diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 6ce60ab..e809a6b 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,13 +1,16 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; +using OneOf; +using Server.Bll.Exceptions; using Server.Bll.Models; namespace Server.Bll.Services.Interfaces { - public interface IRoomCoordinator + public interface IRoomService { - Task CreateRoom(); - Task JoinRoom(); - Task UpdateRoom(); - Task DeleteRoom(); + Task> CreateRoom(int userId, bool isPrivate = false); + Task> JoinRoom(int userId, string roomCode); + Task> GetRoom(int roomId); + Task UpdateRoom(RoomModel room); + Task DeleteRoom(int userId, int roomId); } -} +} \ No newline at end of file diff --git a/Server.Bll/Services/RockPaperScissors.cs b/Server.Bll/Services/RockPaperScissors.cs deleted file mode 100644 index b406454..0000000 --- a/Server.Bll/Services/RockPaperScissors.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Server.Bll.Services -{ - public static class RockPaperScissors - { - - } - -} \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 78f0043..5d37824 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -1,8 +1,14 @@ +using System; +using System.Linq; +using System.Net; using System.Threading.Tasks; +using Mapster; using Microsoft.EntityFrameworkCore; using OneOf; using Server.Bll.Exceptions; +using Server.Bll.Mapper; using Server.Bll.Models; +using Server.Bll.Services.Interfaces; using Server.Dal.Context; using Server.Dal.Entities; @@ -12,39 +18,122 @@ public class RoomService : IRoomService { private readonly DbSet _rooms; private readonly ServerContext _repository; - + public RoomService(ServerContext repository) { _repository = repository; _rooms = _repository.Rooms; } - public async Task> CreateRoom(int userId) + public async Task> CreateRoom(int userId, bool isPrivate = false) { - throw new System.NotImplementedException(); + var doesRoomExist = await _repository.RoomPlayersEnumerable + .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId || x.SecondPlayerId == userId); + if (doesRoomExist != null) + return new RoomException(ExceptionTemplates.TwinkRoom, (int) HttpStatusCode.BadRequest); + + var room = new Room + { + IsPrivate = isPrivate, + RoomCode = Guid.NewGuid().ToString("n")[..8], + IsReady = false, + IsFull = false, + CreationTime = DateTimeOffset.Now, + IsRoundEnded = false, + }; + await _rooms.AddAsync(room); + await _repository.SaveChangesAsync(); + + var thisUser = await _repository.Accounts.FindAsync(userId); + + var newRoomPlayers = new RoomPlayers + { + RoomId = room.Id, + FirstPlayerId = userId, + FirstPlayer = thisUser + }; + + await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); + room.RoomPlayers = newRoomPlayers; + _rooms.Update(room); + + await _repository.SaveChangesAsync(); + return room.ToRoomModel(); } - public async Task> GetRoom() + public async Task> JoinRoom(int userId, string roomCode) { - throw new System.NotImplementedException(); + var foundRoom = await _rooms + .Include(x=>x.RoomPlayers). + FirstOrDefaultAsync(x => x.RoomCode == roomCode); + + if (foundRoom == null) + return new RoomException(ExceptionTemplates.RoomNotExists, 400); + + if (foundRoom.RoomPlayers.FirstPlayerId != 0 && foundRoom.RoomPlayers.SecondPlayerId != 0) + return new RoomException(ExceptionTemplates.RoomFull, 400); + + if (foundRoom.RoomPlayers.FirstPlayerId != 0) + { + foundRoom.RoomPlayers.FirstPlayerId = userId; + _rooms.Update(foundRoom); + + await _repository.SaveChangesAsync(); + return foundRoom.Adapt(); + } + + if (foundRoom.RoomPlayers.SecondPlayerId == 0) + return new RoomException(ExceptionTemplates.Unknown, 400); + + foundRoom.RoomPlayers.SecondPlayerId = userId; + _rooms.Update(foundRoom); + + await _repository.SaveChangesAsync(); + return foundRoom.Adapt(); } - public async Task UpdateRoom() + public async Task> GetRoom(int roomId) { - throw new System.NotImplementedException(); + var room = await _rooms.FindAsync(roomId); + + return room != null + ? room.Adapt() + : new RoomException(ExceptionTemplates.RoomNotExists, 400); } - public async Task DeleteRoom() + public async Task UpdateRoom(RoomModel room) { - throw new System.NotImplementedException(); + var thisRoom = await _rooms.FindAsync(room.Id); + if ( thisRoom == null) + return 400; + var updatedRoom = room.Adapt(); + + thisRoom.IsFull = updatedRoom.IsFull; + thisRoom.IsPrivate = updatedRoom.IsPrivate; + thisRoom.IsReady = updatedRoom.IsReady; + thisRoom.RoundId = updatedRoom.RoundId; + + return _repository.Entry(thisRoom).Properties.Any(x => x.IsModified) + ? 400 + : 200; } - } - public interface IRoomService - { - Task> CreateRoom(int userId); - Task> GetRoom(); - Task UpdateRoom(); - Task DeleteRoom(); + public async Task DeleteRoom(int userId, int roomId) + { + var thisRoom = await _rooms + .Include(x=>x.RoomPlayers) + .FirstOrDefaultAsync(x=>x.Id == roomId); + if (thisRoom == null) + return 400; + if (thisRoom.RoomPlayers.FirstPlayerId != userId) + return 400; + if (thisRoom.RoomPlayers.SecondPlayerId != userId) + return 400; + + _rooms.Remove(thisRoom); + + await _repository.SaveChangesAsync(); + return 200; + } } } \ No newline at end of file diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index e56db10..4513500 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -13,5 +13,32 @@ public class ServerContext : DbContext public ServerContext(DbContextOptions contextOptions) :base(contextOptions) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(invite => invite.FirstPlayer) + .WithMany(user => user.FirstPlayer) + .HasForeignKey(invite => invite.FirstPlayerId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(players => players.Room) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne(x => x.RoomPlayers) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne(invite => invite.SecondPlayer) + .WithMany(user => user.SecondPlayer) + .HasForeignKey(invite => invite.SecondPlayerId) + .OnDelete(DeleteBehavior.NoAction); + } } + + } \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index b6913b0..12cf89f 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Dal.Entities @@ -34,9 +35,7 @@ public class Account [ForeignKey("StatisticsId")] public virtual Statistics Statistics { get; set; } - public int? RoomPlayerId { get; set; } - - [ForeignKey("RoomPlayerId")] - public virtual RoomPlayers RoomPlayers { get; set; } + public virtual ICollection FirstPlayer { get; set; } + public virtual ICollection SecondPlayer { get; set; } } } \ No newline at end of file diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index 7110717..749cb98 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -14,10 +14,15 @@ public class Room [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + /// + /// Special code to join a room + /// + public string RoomCode { get; set; } + /// /// Id of current round /// - public int RoundId { get; set; } + public int? RoundId { get; set; } /// /// Round, linked to this room @@ -25,7 +30,7 @@ public class Room [ForeignKey("RoundId")] public virtual Round Round { get; set; } - public int RoomPlayerId { get; set; } + public int? RoomPlayerId { get; set; } [ForeignKey("RoomPlayerId")] public virtual RoomPlayers RoomPlayers { get; set; } @@ -48,7 +53,7 @@ public class Room /// /// Creation date. After 5 minutes of inactivity will be deleted /// - public DateTime CreationTime { get; set; } + public DateTimeOffset CreationTime { get; set; } /// /// Flag is current count has ended diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index 8a92960..144b3f6 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -11,21 +11,22 @@ public class RoomPlayers [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } - public int RoomId { get; set; } [ForeignKey("RoomId")] public virtual Room Room { get; set; } - - public virtual ICollection Accounts { get; set; } - + public int? FirstPlayerId { get; set; } + [ForeignKey("FirstPlayerId")] + public virtual Account FirstPlayer { get; set; } public int FirstPlayerMove { get; set; } - + public int? SecondPlayerId { get; set; } + [ForeignKey("SecondPlayerId")] + public virtual Account SecondPlayer { get; set; } public int SecondPlayerMove { get; set; } - - public int RoundId { get; set; } + public int? RoundId { get; set; } [ForeignKey("RoundId")] public virtual Round Round { get; set; } + } } \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index a32bc60..2180358 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -20,12 +20,12 @@ public class Round public DateTimeOffset TimeFinished { get; set; } - public int WinnerId { get; set; } + public int? WinnerId { get; set; } [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } - public int LoserId { get; set; } + public int? LoserId { get; set; } [ForeignKey("LoserId")] public virtual Account Loser { get; set; } diff --git a/Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs b/Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs similarity index 79% rename from Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs rename to Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs index c6d84ab..bc47735 100644 --- a/Server.Dal/Migrations/20210711202013_InitialCommit.Designer.cs +++ b/Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs @@ -9,14 +9,14 @@ namespace Server.Dal.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20210711202013_InitialCommit")] - partial class InitialCommit + [Migration("20210713165841_NULLABLES")] + partial class NULLABLES { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.5"); + .HasAnnotation("ProductVersion", "5.0.7"); modelBuilder.Entity("Server.Dal.Entities.Account", b => { @@ -30,16 +30,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - b.Property("StatisticsId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("RoomPlayerId"); - b.HasIndex("StatisticsId"); b.ToTable("Accounts"); @@ -51,7 +46,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreationTime") + b.Property("CreationTime") .HasColumnType("TEXT"); b.Property("IsFull") @@ -66,10 +61,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsRoundEnded") .HasColumnType("INTEGER"); - b.Property("RoomPlayerId") + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("RoundId") + b.Property("RoundId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -88,13 +86,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + b.Property("FirstPlayerMove") .HasColumnType("INTEGER"); b.Property("RoomId") .HasColumnType("INTEGER"); - b.Property("RoundId") + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") .HasColumnType("INTEGER"); b.Property("SecondPlayerMove") @@ -102,10 +106,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("RoomId"); + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); b.HasIndex("RoundId"); + b.HasIndex("SecondPlayerId"); + b.ToTable("RoomPlayers"); }); @@ -118,7 +127,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LoserId") + b.Property("LoserId") .HasColumnType("INTEGER"); b.Property("RoomPlayersId") @@ -127,7 +136,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TimeFinished") .HasColumnType("TEXT"); - b.Property("WinnerId") + b.Property("WinnerId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -186,16 +195,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany("Accounts") - .HasForeignKey("RoomPlayerId"); - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() .HasForeignKey("StatisticsId"); - b.Navigation("RoomPlayers"); - b.Navigation("Statistics"); }); @@ -204,14 +207,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithOne() .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .OnDelete(DeleteBehavior.Cascade); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoundId"); b.Navigation("RoomPlayers"); @@ -220,30 +220,40 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + b.HasOne("Server.Dal.Entities.Room", "Room") - .WithMany() - .HasForeignKey("RoomId") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); b.Navigation("Room"); b.Navigation("Round"); + + b.Navigation("SecondPlayer"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => { b.HasOne("Server.Dal.Entities.Account", "Loser") .WithMany() - .HasForeignKey("LoserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("LoserId"); b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany() @@ -253,9 +263,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Account", "Winner") .WithMany() - .HasForeignKey("WinnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("WinnerId"); b.Navigation("Loser"); @@ -275,9 +283,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Navigation("Accounts"); + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); }); #pragma warning restore 612, 618 } diff --git a/Server.Dal/Migrations/20210711202013_InitialCommit.cs b/Server.Dal/Migrations/20210713165841_NULLABLES.cs similarity index 75% rename from Server.Dal/Migrations/20210711202013_InitialCommit.cs rename to Server.Dal/Migrations/20210713165841_NULLABLES.cs index 83c9d73..ff412ec 100644 --- a/Server.Dal/Migrations/20210711202013_InitialCommit.cs +++ b/Server.Dal/Migrations/20210713165841_NULLABLES.cs @@ -3,10 +3,28 @@ namespace Server.Dal.Migrations { - public partial class InitialCommit : Migration + public partial class NULLABLES : Migration { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.CreateTable( + name: "RoomPlayers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomId = table.Column(type: "INTEGER", nullable: false), + FirstPlayerId = table.Column(type: "INTEGER", nullable: true), + FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), + SecondPlayerId = table.Column(type: "INTEGER", nullable: true), + SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), + RoundId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomPlayers", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Rounds", columns: table => new @@ -16,12 +34,50 @@ protected override void Up(MigrationBuilder migrationBuilder) RoomPlayersId = table.Column(type: "INTEGER", nullable: false), IsFinished = table.Column(type: "INTEGER", nullable: false), TimeFinished = table.Column(type: "TEXT", nullable: false), - WinnerId = table.Column(type: "INTEGER", nullable: false), - LoserId = table.Column(type: "INTEGER", nullable: false) + WinnerId = table.Column(type: "INTEGER", nullable: true), + LoserId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { table.PrimaryKey("PK_Rounds", x => x.Id); + table.ForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + column: x => x.RoomPlayersId, + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Rooms", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoomCode = table.Column(type: "TEXT", nullable: true), + RoundId = table.Column(type: "INTEGER", nullable: true), + RoomPlayerId = table.Column(type: "INTEGER", nullable: true), + IsPrivate = table.Column(type: "INTEGER", nullable: false), + IsReady = table.Column(type: "INTEGER", nullable: false), + IsFull = table.Column(type: "INTEGER", nullable: false), + CreationTime = table.Column(type: "TEXT", nullable: false), + IsRoundEnded = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rooms", x => x.Id); + table.ForeignKey( + name: "FK_Rooms_RoomPlayers_RoomPlayerId", + column: x => x.RoomPlayerId, + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Rooms_Rounds_RoundId", + column: x => x.RoundId, + principalTable: "Rounds", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( @@ -54,8 +110,7 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("Sqlite:Autoincrement", true), Login = table.Column(type: "TEXT", nullable: true), Password = table.Column(type: "TEXT", nullable: true), - StatisticsId = table.Column(type: "INTEGER", nullable: true), - RoomPlayerId = table.Column(type: "INTEGER", nullable: true) + StatisticsId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { @@ -68,79 +123,32 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Restrict); }); - migrationBuilder.CreateTable( - name: "Rooms", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoundId = table.Column(type: "INTEGER", nullable: false), - RoomPlayerId = table.Column(type: "INTEGER", nullable: false), - IsPrivate = table.Column(type: "INTEGER", nullable: false), - IsReady = table.Column(type: "INTEGER", nullable: false), - IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTime = table.Column(type: "TEXT", nullable: false), - IsRoundEnded = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Rooms", x => x.Id); - table.ForeignKey( - name: "FK_Rooms_Rounds_RoundId", - column: x => x.RoundId, - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "RoomPlayers", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoomId = table.Column(type: "INTEGER", nullable: false), - FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), - SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), - RoundId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_RoomPlayers", x => x.Id); - table.ForeignKey( - name: "FK_RoomPlayers_Rooms_RoomId", - column: x => x.RoomId, - principalTable: "Rooms", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_RoomPlayers_Rounds_RoundId", - column: x => x.RoundId, - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Accounts_RoomPlayerId", - table: "Accounts", - column: "RoomPlayerId"); - migrationBuilder.CreateIndex( name: "IX_Accounts_StatisticsId", table: "Accounts", column: "StatisticsId"); + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_FirstPlayerId", + table: "RoomPlayers", + column: "FirstPlayerId"); + migrationBuilder.CreateIndex( name: "IX_RoomPlayers_RoomId", table: "RoomPlayers", - column: "RoomId"); + column: "RoomId", + unique: true); migrationBuilder.CreateIndex( name: "IX_RoomPlayers_RoundId", table: "RoomPlayers", column: "RoundId"); + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_SecondPlayerId", + table: "RoomPlayers", + column: "SecondPlayerId"); + migrationBuilder.CreateIndex( name: "IX_Rooms_RoomPlayerId", table: "Rooms", @@ -173,50 +181,56 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "AccountId"); migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds", - column: "LoserId", + name: "FK_RoomPlayers_Accounts_FirstPlayerId", + table: "RoomPlayers", + column: "FirstPlayerId", principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + principalColumn: "Id"); migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds", - column: "WinnerId", + name: "FK_RoomPlayers_Accounts_SecondPlayerId", + table: "RoomPlayers", + column: "SecondPlayerId", principalTable: "Accounts", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_RoomPlayers_Rooms_RoomId", + table: "RoomPlayers", + column: "RoomId", + principalTable: "Rooms", principalColumn: "Id", onDelete: ReferentialAction.Cascade); migrationBuilder.AddForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId", - principalTable: "RoomPlayers", + name: "FK_RoomPlayers_Rounds_RoundId", + table: "RoomPlayers", + column: "RoundId", + principalTable: "Rounds", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); migrationBuilder.AddForeignKey( - name: "FK_Statistics_Accounts_AccountId", - table: "Statistics", - column: "AccountId", + name: "FK_Rounds_Accounts_LoserId", + table: "Rounds", + column: "LoserId", principalTable: "Accounts", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); migrationBuilder.AddForeignKey( - name: "FK_Accounts_RoomPlayers_RoomPlayerId", - table: "Accounts", - column: "RoomPlayerId", - principalTable: "RoomPlayers", + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds", + column: "WinnerId", + principalTable: "Accounts", principalColumn: "Id", onDelete: ReferentialAction.Restrict); migrationBuilder.AddForeignKey( - name: "FK_Rooms_RoomPlayers_RoomPlayerId", - table: "Rooms", - column: "RoomPlayerId", - principalTable: "RoomPlayers", + name: "FK_Statistics_Accounts_AccountId", + table: "Statistics", + column: "AccountId", + principalTable: "Accounts", principalColumn: "Id", onDelete: ReferentialAction.Cascade); } @@ -224,35 +238,47 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropForeignKey( - name: "FK_Accounts_RoomPlayers_RoomPlayerId", + name: "FK_Accounts_Statistics_StatisticsId", table: "Accounts"); migrationBuilder.DropForeignKey( - name: "FK_Rooms_RoomPlayers_RoomPlayerId", - table: "Rooms"); + name: "FK_RoomPlayers_Accounts_FirstPlayerId", + table: "RoomPlayers"); + + migrationBuilder.DropForeignKey( + name: "FK_RoomPlayers_Accounts_SecondPlayerId", + table: "RoomPlayers"); migrationBuilder.DropForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", + name: "FK_Rounds_Accounts_LoserId", table: "Rounds"); migrationBuilder.DropForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts"); + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds"); + + migrationBuilder.DropForeignKey( + name: "FK_RoomPlayers_Rooms_RoomId", + table: "RoomPlayers"); + + migrationBuilder.DropForeignKey( + name: "FK_RoomPlayers_Rounds_RoundId", + table: "RoomPlayers"); migrationBuilder.DropTable( - name: "RoomPlayers"); + name: "Statistics"); migrationBuilder.DropTable( - name: "Rooms"); + name: "Accounts"); migrationBuilder.DropTable( - name: "Rounds"); + name: "Rooms"); migrationBuilder.DropTable( - name: "Statistics"); + name: "Rounds"); migrationBuilder.DropTable( - name: "Accounts"); + name: "RoomPlayers"); } } } diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index cb7e21b..e437ad8 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -14,7 +14,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.5"); + .HasAnnotation("ProductVersion", "5.0.7"); modelBuilder.Entity("Server.Dal.Entities.Account", b => { @@ -28,16 +28,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - b.Property("StatisticsId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("RoomPlayerId"); - b.HasIndex("StatisticsId"); b.ToTable("Accounts"); @@ -49,7 +44,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreationTime") + b.Property("CreationTime") .HasColumnType("TEXT"); b.Property("IsFull") @@ -64,10 +59,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsRoundEnded") .HasColumnType("INTEGER"); - b.Property("RoomPlayerId") + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") .HasColumnType("INTEGER"); - b.Property("RoundId") + b.Property("RoundId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -86,13 +84,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + b.Property("FirstPlayerMove") .HasColumnType("INTEGER"); b.Property("RoomId") .HasColumnType("INTEGER"); - b.Property("RoundId") + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") .HasColumnType("INTEGER"); b.Property("SecondPlayerMove") @@ -100,10 +104,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("RoomId"); + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); b.HasIndex("RoundId"); + b.HasIndex("SecondPlayerId"); + b.ToTable("RoomPlayers"); }); @@ -116,7 +125,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LoserId") + b.Property("LoserId") .HasColumnType("INTEGER"); b.Property("RoomPlayersId") @@ -125,7 +134,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TimeFinished") .HasColumnType("TEXT"); - b.Property("WinnerId") + b.Property("WinnerId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -184,16 +193,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany("Accounts") - .HasForeignKey("RoomPlayerId"); - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() .HasForeignKey("StatisticsId"); - b.Navigation("RoomPlayers"); - b.Navigation("Statistics"); }); @@ -202,14 +205,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithOne() .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .OnDelete(DeleteBehavior.Cascade); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoundId"); b.Navigation("RoomPlayers"); @@ -218,30 +218,40 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + b.HasOne("Server.Dal.Entities.Room", "Room") - .WithMany() - .HasForeignKey("RoomId") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Server.Dal.Entities.Round", "Round") .WithMany() - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); b.Navigation("Room"); b.Navigation("Round"); + + b.Navigation("SecondPlayer"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => { b.HasOne("Server.Dal.Entities.Account", "Loser") .WithMany() - .HasForeignKey("LoserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("LoserId"); b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") .WithMany() @@ -251,9 +261,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Server.Dal.Entities.Account", "Winner") .WithMany() - .HasForeignKey("WinnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("WinnerId"); b.Navigation("Loser"); @@ -273,9 +281,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Navigation("Accounts"); + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); }); #pragma warning restore 612, 618 } diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 504bacb..3e1918d 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -5,6 +5,9 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Server.Authentication.Models.Interfaces; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; namespace Server.Controllers { @@ -14,40 +17,65 @@ namespace Server.Controllers [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RoomController : ControllerBase { + private readonly IRoomService _roomService; + private readonly IApplicationUser _applicationUser; + private int UserId => _applicationUser.Id; - public RoomController() + public RoomController(IRoomService roomService, + IApplicationUser applicationUser) { + _roomService = roomService; + _applicationUser = applicationUser; } [HttpPost] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] public async Task CreateRoom() { - throw new NotImplementedException(); + var newRoom = await _roomService.CreateRoom(UserId); + + return newRoom.Match( + Ok, + exception => BadRequest(exception)); } [HttpPost] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task JoinRoom() + public async Task JoinRoom([FromQuery] string roomCode) { - throw new NotImplementedException(); + var result = await _roomService.JoinRoom(UserId, roomCode); + return result.Match( + Ok, + exception => BadRequest(exception)); } [HttpGet] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateRoom() + public async Task UpdateRoom([FromBody] RoomModel roomModel) { - throw new NotImplementedException(); + var updateResponse = await _roomService.UpdateRoom(roomModel); + + return updateResponse switch + { + 200 => Ok(), + _ => BadRequest() + }; } [HttpDelete] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task DeleteRoom(string roomId) + public async Task DeleteRoom([FromQuery] int roomId) { - throw new NotImplementedException(); + var deleteResponse = await _roomService.DeleteRoom(UserId,roomId); + + return deleteResponse switch + { + 200 => Ok(), + _ => BadRequest() + }; } } } diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index 70827ff..d19c2dd 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Server.Host.Extensions +namespace Server.Extensions { internal class LoggingMiddleware { diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index 2d370b1..8566dd5 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -1,13 +1,20 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Server.Authentication.Models; +using Server.Authentication.Models.Interfaces; +using Server.Bll.Services; +using Server.Bll.Services.Interfaces; -namespace Server.Host.Extensions +namespace Server.Extensions { public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - throw new NotImplementedException(); + service.AddTransient(); + service.AddHttpContextAccessor(); + + return service + .AddTransient(); } } } \ No newline at end of file diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index d28a739..5aea1ba 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -27,6 +27,9 @@ public void ConfigureServices(IServiceCollection services) .AddSwagger() .AddAuthentications(); + // Adding services + services.AddServices(); + services.AddControllers(); services.AddCors(); From ee08dff7c6a69d84203fe3e0091c0b4ea9963a56 Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Wed, 14 Jul 2021 13:51:39 +0300 Subject: [PATCH 15/56] Implemented CleanerHostedService.cs --- Server.Bll/Exceptions/ExceptionTemplates.cs | 2 +- .../HostedServices/CleanerHostedService.cs | 51 +++++++++++++++++++ Server.Bll/Mapper/RoomMapper.cs | 6 +-- .../Services/Interfaces/IRoomService.cs | 1 + Server.Bll/Services/RoomService.cs | 49 ++++++++++++++++-- Server.Dal/Entities/Room.cs | 2 +- Server.Dal/Entities/Round.cs | 2 +- Server.Dal/Entities/Statistics.cs | 2 - .../Migrations/ServerContextModelSnapshot.cs | 8 +-- Server.Host/Extensions/ServiceExtension.cs | 16 ++++-- 10 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 Server.Bll/HostedServices/CleanerHostedService.cs diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index 6abf800..ee0a915 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -6,6 +6,6 @@ public static class ExceptionTemplates public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; public const string RoomNotExists = "Room does not exist."; public const string RoomFull = "This room is full."; - + public const string AlreadyInRoom = "You are already in room."; } } \ No newline at end of file diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs new file mode 100644 index 0000000..6d95612 --- /dev/null +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Server.Bll.Services; + +namespace Server.Bll.HostedServices +{ + public class CleanerHostedService : IHostedService + { + private readonly IServiceScopeFactory _serviceProvider; + private readonly ILogger _logger; + private Timer _timer; + + // todo: options of max time + + public CleanerHostedService( + ILogger logger, + IServiceScopeFactory serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(GenerateDailyQuests, _serviceProvider, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(1)); + return Task.CompletedTask; + } + + private async void GenerateDailyQuests(object state) + { + _logger.LogInformation("Starting Cleaner service"); + var factory = (IServiceScopeFactory) state; + using var scope = factory.CreateScope(); + var roomService = scope.ServiceProvider.GetRequiredService(); + var rooms = await roomService.GetRoomsByDate(TimeSpan.FromMinutes(5)); + if (rooms is not {Length: > 0}) return; + await roomService.RemoveRoomRange(rooms); + _logger.LogInformation("Cleaned {0} entities of room",rooms.Length); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Dispose(); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Server.Bll/Mapper/RoomMapper.cs b/Server.Bll/Mapper/RoomMapper.cs index ce6fecf..c2c3b28 100644 --- a/Server.Bll/Mapper/RoomMapper.cs +++ b/Server.Bll/Mapper/RoomMapper.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using Server.Bll.Models; using Server.Dal.Entities; @@ -23,7 +23,7 @@ public static RoomModel ToRoomModel(this Room room) IsPrivate = room.IsPrivate, IsReady = room.IsReady, IsFull = room.IsFull, - CreationTime = room.CreationTime, + CreationTime = new DateTime(room.CreationTimeTicks), IsRoundEnded = room.IsRoundEnded, }; if (room.RoomPlayers.FirstPlayer != null) @@ -51,7 +51,7 @@ public static RoomModel ToRoomModel(this Room room) IsPrivate = room.IsPrivate, IsReady = room.IsReady, IsFull = room.IsFull, - CreationTime = room.CreationTime, + CreationTime = new DateTime(room.CreationTimeTicks), IsRoundEnded = room.IsRoundEnded, }; } diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index e809a6b..9905ee3 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using OneOf; using Server.Bll.Exceptions; diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 5d37824..fd78343 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -14,7 +14,7 @@ namespace Server.Bll.Services { - public class RoomService : IRoomService + public class RoomService : IRoomService, IHostedRoomService { private readonly DbSet _rooms; private readonly ServerContext _repository; @@ -38,7 +38,7 @@ public async Task> CreateRoom(int userId, bool i RoomCode = Guid.NewGuid().ToString("n")[..8], IsReady = false, IsFull = false, - CreationTime = DateTimeOffset.Now, + CreationTimeTicks = DateTimeOffset.Now.Ticks, IsRoundEnded = false, }; await _rooms.AddAsync(room); @@ -70,6 +70,8 @@ public async Task> JoinRoom(int userId, string r if (foundRoom == null) return new RoomException(ExceptionTemplates.RoomNotExists, 400); + if (foundRoom.RoomPlayers.FirstPlayerId == userId || foundRoom.RoomPlayers.SecondPlayerId == userId) + return new RoomException(ExceptionTemplates.AlreadyInRoom, 400); if (foundRoom.RoomPlayers.FirstPlayerId != 0 && foundRoom.RoomPlayers.SecondPlayerId != 0) return new RoomException(ExceptionTemplates.RoomFull, 400); @@ -100,7 +102,7 @@ public async Task> GetRoom(int roomId) ? room.Adapt() : new RoomException(ExceptionTemplates.RoomNotExists, 400); } - + public async Task UpdateRoom(RoomModel room) { var thisRoom = await _rooms.FindAsync(room.Id); @@ -135,5 +137,46 @@ public async Task> GetRoom(int roomId) await _repository.SaveChangesAsync(); return 200; } + + /// + /// ONLY TO BE USED WITH I HOSTED SERVICE. REMOVES RANGE OF ROOMS + /// + /// + /// + [Obsolete(message:"Should be carefully used")] + public async Task RemoveRoomRange(Room[] rooms) + { + try + { + _rooms.RemoveRange(rooms); + + await _repository.SaveChangesAsync(); + return true; + } + catch (Exception) + { + return false; + } + } + /// + /// Only to use with IHOSTEDSERVICE! + /// + /// + /// + [Obsolete(message:"Should be carefully used")] + public async Task GetRoomsByDate(TimeSpan timeSpan) + { + var currentDate = DateTimeOffset.Now.Ticks; + return await _rooms + .Where(x => x.CreationTimeTicks + timeSpan.Ticks < currentDate + && x.RoundId == null) + .ToArrayAsync(); + } + } + + public interface IHostedRoomService + { + Task GetRoomsByDate(TimeSpan timeSpan); + Task RemoveRoomRange(Room[] rooms); } } \ No newline at end of file diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index 749cb98..c31df36 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -53,7 +53,7 @@ public class Room /// /// Creation date. After 5 minutes of inactivity will be deleted /// - public DateTimeOffset CreationTime { get; set; } + public long CreationTimeTicks { get; set; } /// /// Flag is current count has ended diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 2180358..08b3dfd 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -18,7 +18,7 @@ public class Round public bool IsFinished { get; set; } - public DateTimeOffset TimeFinished { get; set; } + public long TimeFinishedTicks { get; set; } public int? WinnerId { get; set; } diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index a1e31b0..2b62dc6 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -71,7 +71,5 @@ public class Statistics /// public int? Score { get; set; } - - } } \ No newline at end of file diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index e437ad8..35bc598 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -44,8 +44,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreationTime") - .HasColumnType("TEXT"); + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); b.Property("IsFull") .HasColumnType("INTEGER"); @@ -131,8 +131,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RoomPlayersId") .HasColumnType("INTEGER"); - b.Property("TimeFinished") - .HasColumnType("TEXT"); + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); b.Property("WinnerId") .HasColumnType("INTEGER"); diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index 8566dd5..1496194 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Server.Authentication.Models; using Server.Authentication.Models.Interfaces; +using Server.Bll.HostedServices; using Server.Bll.Services; using Server.Bll.Services.Interfaces; @@ -10,11 +11,18 @@ public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - service.AddTransient(); + service.AddTransient() + .AddHostedService(); service.AddHttpContextAccessor(); - - return service - .AddTransient(); + + // In this way I am registering multiple interfaces to one Transient instance of RoomService; + service + .AddTransient() + .AddTransient(provider => provider.GetRequiredService()) + .AddTransient(provider => provider.GetRequiredService()); + + return service; + } } } \ No newline at end of file From bd0821eb19946da7360b2dd5dacd2fceb9a1e2cf Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Wed, 14 Jul 2021 13:51:50 +0300 Subject: [PATCH 16/56] Added new Migrations --- ...8_ChangedDateTimeOffsetToTicks.Designer.cs | 295 ++++++++++++++++++ ...0714103428_ChangedDateTimeOffsetToTicks.cs | 58 ++++ 2 files changed, 353 insertions(+) create mode 100644 Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs create mode 100644 Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs diff --git a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs new file mode 100644 index 0000000..ee69673 --- /dev/null +++ b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs @@ -0,0 +1,295 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210714103428_ChangedDateTimeOffsetToTicks")] + partial class ChangedDateTimeOffsetToTicks + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs new file mode 100644 index 0000000..8d2aade --- /dev/null +++ b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class ChangedDateTimeOffsetToTicks : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TimeFinished", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "CreationTime", + table: "Rooms"); + + migrationBuilder.AddColumn( + name: "TimeFinishedTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "CreationTimeTicks", + table: "Rooms", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TimeFinishedTicks", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "CreationTimeTicks", + table: "Rooms"); + + migrationBuilder.AddColumn( + name: "TimeFinished", + table: "Rounds", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "CreationTime", + table: "Rooms", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + } +} From 3934c901510b360a77541de956577e1865c9e68c Mon Sep 17 00:00:00 2001 From: "ihor.volokhovych" Date: Thu, 15 Jul 2021 22:36:36 +0300 Subject: [PATCH 17/56] Somewhat refactored Client --- Client/Client.csproj | 3 +- Client/Extensions/JsonExtension.cs | 39 +++ Client/Menus/AccountMenu.cs | 65 ++-- Client/Menus/IAccountMenu.cs | 7 +- Client/Menus/MainMenu.cs | 18 +- Client/Menus/StartMenu.cs | 41 ++- Client/Models/Account.cs | 11 +- Client/Models/ErrorModel.cs | 12 + Client/Models/Interfaces/IAccount.cs | 1 - Client/Program.cs | 13 +- .../RequestProcessor/Impl/RequestHandler.cs | 24 +- .../RequestProcessor/Impl/RequestPerformer.cs | 27 +- .../RequestModels/IRequestOptions.cs | 6 +- .../RequestModels/Impl/RequestOptions.cs | 1 + Client/Services/StringPlaceholder.cs | 15 +- .../HostedServices/CleanerHostedService.cs | 6 +- Server.Dal/Entities/Round.cs | 9 +- ...183223_AddedTimeChangedToRound.Designer.cs | 298 ++++++++++++++++++ .../20210715183223_AddedTimeChangedToRound.cs | 24 ++ ...ddedTimeChangedToRoundREVERTED.Designer.cs | 295 +++++++++++++++++ ...5183553_AddedTimeChangedToRoundREVERTED.cs | 24 ++ Server.Host/Controllers/AccountController.cs | 7 +- 22 files changed, 825 insertions(+), 121 deletions(-) create mode 100644 Client/Extensions/JsonExtension.cs create mode 100644 Client/Models/ErrorModel.cs create mode 100644 Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs create mode 100644 Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs create mode 100644 Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs create mode 100644 Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs diff --git a/Client/Client.csproj b/Client/Client.csproj index 591893e..8713b38 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -7,8 +7,7 @@ - - + diff --git a/Client/Extensions/JsonExtension.cs b/Client/Extensions/JsonExtension.cs new file mode 100644 index 0000000..c6a28c7 --- /dev/null +++ b/Client/Extensions/JsonExtension.cs @@ -0,0 +1,39 @@ +using Client.Services.RequestProcessor.RequestModels; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; + +namespace Client.Extensions +{ + public static class JsonExtension + { + private const string Schema = @"{ +'code': {'type': 'integer'}, +'message': {'type': 'string'}, +}"; + + private static JSchema JSchema => JSchema.Parse(Schema); + + public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() + { + if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) + { + deserialized = default; + return false; + } + + var jObject = JObject.Parse(json.Content); + + var isValid = jObject.IsValid(JSchema); + + if (!isValid) + { + deserialized = default; + return false; + } + + deserialized = JsonConvert.DeserializeObject(json.Content); + return true; + } + } +} \ No newline at end of file diff --git a/Client/Menus/AccountMenu.cs b/Client/Menus/AccountMenu.cs index 88eae68..fee29b2 100644 --- a/Client/Menus/AccountMenu.cs +++ b/Client/Menus/AccountMenu.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using Client.Extensions; using Client.Models; using Client.Services; using Client.Services.RequestProcessor; @@ -18,7 +20,7 @@ public AccountMenu(IRequestPerformer performer) _performer = performer; } - public async Task Register() + public async Task RegisterAsync() { TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + "Please enter the required details\n" + @@ -27,83 +29,74 @@ public async Task Register() { Login = new StringPlaceholder().BuildString("Login"), Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password"), - LastRequest = DateTime.Now + new StringPlaceholder(StringDestination.Password).BuildString("Password") }; var options = new RequestOptions { ContentType = "application/json", Body = JsonConvert.SerializeObject(registrationAccount), - Address = "user/register", + Address = "account/register", IsValid = true, Method = Services.RequestModels.RequestMethod.Post, Name = "Registration" }; var reachedResponse = await _performer.PerformRequestAsync(options); - if (reachedResponse.Code == (int) HttpStatusCode.Created) + + if (reachedResponse.TryParseJson(out var errorModel)) { - - TextWrite.Print(reachedResponse.Content, ConsoleColor.Green); - return true; + TextWrite.Print(errorModel.Message, ConsoleColor.Red); + return false; } - - TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); - return false; + + TextWrite.Print("Successfully registered!", ConsoleColor.Green); + return true; } - public async Task<(string sessionId, Account inputAccount)> LogIn() + public async Task<(string token, Account inputAccount)> LoginAsync() { var inputAccount = new Account { Login = new StringPlaceholder().BuildString("Login"), Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password", true), - LastRequest = DateTime.Now + new StringPlaceholder(StringDestination.Password).BuildString("Password", true) }; var options = new RequestOptions { ContentType = "application/json", Body = JsonConvert.SerializeObject(inputAccount), - Address = "user/login", + Address = "account/login", IsValid = true, Method = Services.RequestModels.RequestMethod.Post, Name = "Login" }; var reachedResponse = await _performer.PerformRequestAsync(options); - if (reachedResponse.Code == (int) HttpStatusCode.OK) + + if (reachedResponse.TryParseJson(out var tokenModel)) { - TextWrite.Print(reachedResponse.Content, ConsoleColor.Green); - - //returns sessionId - var sessionId = reachedResponse.Content.Replace("\"","").Trim(); - TextWrite.Print($"Successfully signed in! session id : {sessionId}", ConsoleColor.DarkGreen); - return (sessionId, inputAccount); + TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); + return (tokenModel.Token, inputAccount); } - TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); - + var error = JsonConvert.DeserializeObject(reachedResponse.Content); + if(error is null) return (null, null); + + TextWrite.Print(error.Message, ConsoleColor.Red); return (null, null); } - public async Task Logout(string sessionId) + public async Task LogoutAsync(string token) { var options = new RequestOptions { - Address = $"user/logout/{sessionId}", + Headers = new Dictionary{{"Authorization",token}}, + Address = $"user/logout/{token}", IsValid = true, Method = Services.RequestModels.RequestMethod.Get }; - var reachedResponse = await _performer.PerformRequestAsync(options); - if (reachedResponse.Code == 200) - { - TextWrite.Print("Successfully signed out", ConsoleColor.Green); - return true; - } - - TextWrite.Print(reachedResponse.Content, ConsoleColor.Red); - - return false; + await _performer.PerformRequestAsync(options); + TextWrite.Print("Successfully signed out", ConsoleColor.Green); + return true; } } } \ No newline at end of file diff --git a/Client/Menus/IAccountMenu.cs b/Client/Menus/IAccountMenu.cs index acdaa17..37a41d8 100644 --- a/Client/Menus/IAccountMenu.cs +++ b/Client/Menus/IAccountMenu.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; +using Client.Models; namespace Client.Menus { public interface IAccountMenu { - Task Register(); - Task<(string sessionId, Models.Account inputAccount)> LogIn(); - Task Logout(string sessionId); + Task RegisterAsync(); + Task<(string token, Account inputAccount)> LoginAsync(); + Task LogoutAsync(string token); } } \ No newline at end of file diff --git a/Client/Menus/MainMenu.cs b/Client/Menus/MainMenu.cs index 1879bfa..a92184c 100644 --- a/Client/Menus/MainMenu.cs +++ b/Client/Menus/MainMenu.cs @@ -18,14 +18,16 @@ public async Task PlayerMenu() { while (true) { - TextWrite.Print($"***\nHello, {_playerAccount.Login}\n" + - "Please choose option", ConsoleColor.Cyan); - TextWrite.Print("1.\tPlay with bot\n" + - "2\tCreate room\n" + - "3\tJoin Private room\n" + - "4\tJoin Public room\n" + - "5\tShow Statistics\n" + - "6\tLog out", ConsoleColor.Yellow); + TextWrite.Print( + $"***\nHello, {_playerAccount.Login}\n" + + "Please choose option", ConsoleColor.Cyan); + TextWrite.Print( + "1.\tPlay with bot\n" + + "2\tCreate room\n" + + "3\tJoin room\n" + + "4\tSearch open room\n" + + "5\tShow Statistics\n" + + "6\tLog out", ConsoleColor.Yellow); TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs index b156a49..6f9f41d 100644 --- a/Client/Menus/StartMenu.cs +++ b/Client/Menus/StartMenu.cs @@ -22,8 +22,10 @@ public async Task StartAsync() { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; - Greeting(); - TextWrite.Print("\n\nPress any key to show start up menu list!", ConsoleColor.Green); + + await Greeting().ConfigureAwait(false); + TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); + Console.ReadKey(); Console.Clear(); //todo: trying to connect to the server @@ -34,12 +36,11 @@ private async Task Menu(CancellationToken token) { while (true) { - TextWrite.Print("" + - "Please auth to proceed:\n" + - "1.\tSign up\n" + - "2.\tLog in\n" + - "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics - "4.\tExit", ConsoleColor.DarkYellow); + TextWrite.Print("Start menu:\n" + + "1.\tSign up\n" + + "2.\tLog in\n" + + "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics + "4.\tExit", ConsoleColor.DarkYellow); TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); @@ -51,13 +52,13 @@ private async Task Menu(CancellationToken token) var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); if (!passed) { - TextWrite.Print("Unsupported input", ConsoleColor.Red); + TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); continue; } switch (startMenuInput) { - case 1: - await _accountMenu.Register(); + case 1: + await _accountMenu.RegisterAsync(); TextWrite.Print( "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); Console.ReadKey(); @@ -65,18 +66,14 @@ private async Task Menu(CancellationToken token) break; case 2: Account inputAccount; - (SessionId, inputAccount) = await _accountMenu.LogIn(); - if (string.IsNullOrEmpty(SessionId)) - { - Console.WriteLine("Failed to log in"); - Console.Clear(); - } - else + (SessionId, inputAccount) = await _accountMenu.LoginAsync(); + if (!string.IsNullOrEmpty(SessionId)) { Console.ReadKey(); Console.Clear(); await new MainMenu(inputAccount).PlayerMenu(); } + Console.Clear(); break; case 3: /*var statistics = await OverallStatistics(); @@ -88,7 +85,7 @@ private async Task Menu(CancellationToken token) }*/ break; case 4: - if (await _accountMenu.Logout(SessionId)) + if (await _accountMenu.LogoutAsync(SessionId)) { Console.WriteLine("DEBUG: Logged out"); return; @@ -101,19 +98,19 @@ private async Task Menu(CancellationToken token) TextWrite.Print("Unsupported input", ConsoleColor.Red); continue; } - } - } - private static void Greeting() + private static Task Greeting() { TextWrite.Print( + "VERSION 2.0\n" + "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + "You are given the opportunity to compete with other users in this wonderful game,\n" + "or if you don’t have anyone to play, don’t worry,\n" + "you can find a random player or just try your skill with a bot.", ConsoleColor.White); TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs index 44a8703..507b0aa 100644 --- a/Client/Models/Account.cs +++ b/Client/Models/Account.cs @@ -11,8 +11,13 @@ public class Account : IAccount [JsonProperty("Password")] public string Password { get; set; } - - [JsonProperty("LastRequest")] - public DateTime LastRequest { get; set; } + } + + public class TokenModel + { + [JsonProperty("Token")] + public string Token { get; set; } + [JsonProperty("Login")] + public string Login { get; set; } } } diff --git a/Client/Models/ErrorModel.cs b/Client/Models/ErrorModel.cs new file mode 100644 index 0000000..a9d05c0 --- /dev/null +++ b/Client/Models/ErrorModel.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Client.Models +{ + public class ErrorModel + { + [JsonProperty("Code")] + public int Code { get; set; } + [JsonProperty("Message")] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/Client/Models/Interfaces/IAccount.cs b/Client/Models/Interfaces/IAccount.cs index 45711af..6aaf620 100644 --- a/Client/Models/Interfaces/IAccount.cs +++ b/Client/Models/Interfaces/IAccount.cs @@ -6,6 +6,5 @@ public interface IAccount { public string Login { get; set; } public string Password { get; set; } - public DateTime LastRequest { get; set; } } } \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs index 4ddb587..42aa15d 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -12,16 +12,17 @@ private static async Task Main() { try { - var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/")}; - var requestHandler = new RequestHandler(client); - var performer = new RequestPerformer(requestHandler); - //var emulator = new ClientAppEmulator(new RequestPerformer()); - - var startMenu = new StartMenu(performer); + var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; + var clientHandler = new HttpClientHandler(); + var requestHandler = new RequestHandler(client, clientHandler); + var requestPerformer = new RequestPerformer(requestHandler); + + var startMenu = new StartMenu(requestPerformer); return await startMenu.StartAsync(); } catch (Exception) //todo : do this need a message? { + Console.WriteLine("Unknown error occured. Crash."); return -1; } } diff --git a/Client/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Services/RequestProcessor/Impl/RequestHandler.cs index 4f5bc05..8d765e9 100644 --- a/Client/Services/RequestProcessor/Impl/RequestHandler.cs +++ b/Client/Services/RequestProcessor/Impl/RequestHandler.cs @@ -1,9 +1,6 @@ using Client.Services.RequestModels; using Client.Services.RequestProcessor.RequestModels.Impl; using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -14,21 +11,33 @@ namespace Client.Services.RequestProcessor.Impl public class RequestHandler : IRequestHandler { private readonly HttpClient _client; + private readonly HttpClientHandler _httpClientHandler; - public RequestHandler(HttpClient httpClient) + public RequestHandler(HttpClient httpClient, HttpClientHandler httpClientHandler) { _client = httpClient; + _httpClientHandler = httpClientHandler; } public async Task HandleRequestAsync(IRequestOptions requestOptions) { - var handler = new HttpClientHandler(); - handler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, errors) => true; + //var handler = new HttpClientHandler(); + _httpClientHandler + .ServerCertificateCustomValidationCallback + += (_, _, _, _) => true; if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - using var msg = new HttpRequestMessage(MapMethod(requestOptions.Method), new Uri(_client.BaseAddress+requestOptions.Address)); + using var msg = + new HttpRequestMessage(MapMethod(requestOptions.Method), + new Uri(_client.BaseAddress+requestOptions.Address)); try { + if(requestOptions.Headers != null) + foreach (var (key, value) in requestOptions.Headers) + { + msg.Headers.Add(key,value); + } + if (MapMethod(requestOptions.Method) == HttpMethod.Delete) { using var responseD = await _client.SendAsync(msg); @@ -43,6 +52,7 @@ public async Task HandleRequestAsync(IRequestOptions requestOptions) var bodyForPushing = await responseForPushingData.Content.ReadAsStringAsync(); return new Response(true, (int)responseForPushingData.StatusCode, bodyForPushing); } + using var response = await _client.SendAsync(msg); var body = await response.Content.ReadAsStringAsync(); return new Response(true, (int)response.StatusCode, body); diff --git a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs index 972309a..62a572b 100644 --- a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs +++ b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs @@ -12,26 +12,23 @@ namespace Client.Services.RequestProcessor.Impl public class RequestPerformer : IRequestPerformer { private readonly IRequestHandler _requestHandler; - public RequestPerformer( - IRequestHandler requestHandler) + public RequestPerformer(IRequestHandler requestHandler) { _requestHandler = requestHandler; } - //public readonly IRequestHandler RequestHandler; public async Task PerformRequestAsync(IRequestOptions requestOptions) - { - IResponse response = null; + { if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); - if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - try - { - response = await _requestHandler.HandleRequestAsync(requestOptions); - } - catch (TimeoutException) //todo: Probably redo - { - response = new Response(false, 408, null); - } - return response; + if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); + try + { + return await _requestHandler.HandleRequestAsync(requestOptions); + } + catch (TimeoutException) //todo: Probably redo + { + //response = new Response(false, 408, null); + return null; + } } } } diff --git a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs b/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs index 5d0e7e1..75c1475 100644 --- a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs +++ b/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs @@ -1,7 +1,11 @@ -namespace Client.Services.RequestModels +using System.Collections.Generic; +using Client.Services.RequestModels; + +namespace Client.Services.RequestProcessor.RequestModels { public interface IRequestOptions { + Dictionary Headers { get; } string Name { get; } string Address { get; } RequestMethod Method { get; } diff --git a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs b/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs index afa9ef9..fd90722 100644 --- a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs +++ b/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs @@ -9,6 +9,7 @@ namespace Client.Services.RequestProcessor.RequestModels.Impl { public class RequestOptions : IRequestOptions { + public Dictionary Headers { get; set; } public string Name { get; set; } public string Address { get; set; } diff --git a/Client/Services/StringPlaceholder.cs b/Client/Services/StringPlaceholder.cs index 76ea0d0..abffa3a 100644 --- a/Client/Services/StringPlaceholder.cs +++ b/Client/Services/StringPlaceholder.cs @@ -13,7 +13,7 @@ public StringPlaceholder() } public StringPlaceholder(StringDestination destination) { - this._destination = destination; + _destination = destination; } public string BuildString(string msg, bool isNeedConfirmation = false) { @@ -21,15 +21,16 @@ public string BuildString(string msg, bool isNeedConfirmation = false) while (true) { var passwordNotConfirmed = true; - if(_destination == StringDestination.PassportType || _destination == StringDestination.Email) - TextWrite.Print($"What is your {msg}?", ConsoleColor.Yellow); - else - TextWrite.Print($"Try to come up with {msg}?", ConsoleColor.Yellow); + TextWrite.Print( + _destination is StringDestination.PassportType or StringDestination.Email + ? $"What is your {msg}?" + : $"Try to come up with {msg}?", ConsoleColor.Yellow); Console.Write($"{msg}--> "); + output = Console.ReadLine() ?.Trim() .Replace(" ", ""); - if (String.IsNullOrEmpty(output)) + if (string.IsNullOrEmpty(output)) { TextWrite.Print("Wrong data!", ConsoleColor.Red); continue; @@ -46,7 +47,7 @@ public string BuildString(string msg, bool isNeedConfirmation = false) if (_destination == StringDestination.Password) { - if (isNeedConfirmation == true) + if (isNeedConfirmation) break; TextWrite.Print("You need to confirm password!", ConsoleColor.Yellow); do diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index 6d95612..9c64aa6 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -26,7 +26,11 @@ public CleanerHostedService( public Task StartAsync(CancellationToken cancellationToken) { - _timer = new Timer(GenerateDailyQuests, _serviceProvider, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(1)); + _timer = new Timer( + GenerateDailyQuests, + _serviceProvider, + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(10)); return Task.CompletedTask; } diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 08b3dfd..0332333 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -16,19 +16,16 @@ public class Round [ForeignKey("RoomPlayersId")] public virtual RoomPlayers RoomPlayers { get; set; } - public bool IsFinished { get; set; } - - public long TimeFinishedTicks { get; set; } - public int? WinnerId { get; set; } - [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } public int? LoserId { get; set; } - [ForeignKey("LoserId")] public virtual Account Loser { get; set; } + public long TimeFinishedTicks { get; set; } + public bool IsFinished { get; set; } + } } diff --git a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs new file mode 100644 index 0000000..03911a7 --- /dev/null +++ b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs @@ -0,0 +1,298 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210715183223_AddedTimeChangedToRound")] + partial class AddedTimeChangedToRound + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeCreatedTicks") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs new file mode 100644 index 0000000..4c0f3e9 --- /dev/null +++ b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class AddedTimeChangedToRound : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TimeCreatedTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TimeCreatedTicks", + table: "Rounds"); + } + } +} diff --git a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs new file mode 100644 index 0000000..278e4b7 --- /dev/null +++ b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs @@ -0,0 +1,295 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210715183553_AddedTimeChangedToRoundREVERTED")] + partial class AddedTimeChangedToRoundREVERTED + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs new file mode 100644 index 0000000..a052c32 --- /dev/null +++ b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class AddedTimeChangedToRoundREVERTED : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TimeCreatedTicks", + table: "Rounds"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TimeCreatedTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + } + } +} diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index ce874e8..cf84a7f 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -10,7 +10,7 @@ namespace Server.Controllers { [ApiController] - [Route ("[controller]/[action]")] + [Route ("api/v1/account/")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] public class AccountController : ControllerBase @@ -21,7 +21,7 @@ public AccountController(IAuthService authService) { _authService = authService; } - [HttpPost] + [HttpPost("register")] [AllowAnonymous] [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] @@ -35,7 +35,7 @@ public async Task Register(RegisterRequest registerRequest) exception => BadRequest(exception)); } - [HttpPost] + [HttpPost("login")] [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] public async Task Login(AccountDto accountDto) @@ -50,6 +50,7 @@ public async Task Login(AccountDto accountDto) ); } + [HttpGet("logout")] [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] public ActionResult Logout(string sessionId) From 13a36e23204763631ef5173881a8da60d2cde0ff Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 16 Jul 2021 00:10:50 +0300 Subject: [PATCH 18/56] Added Statistics --- Server.Bll/Server.Bll.csproj | 1 + .../Services/Interfaces/IStatisticsService.cs | 12 + Server.Bll/Services/StatisticsService.cs | 32 ++ Server.Dal/Entities/Statistics.cs | 16 +- ...016_DeletedNullablesStatistics.Designer.cs | 295 ++++++++++++++++++ ...210715210016_DeletedNullablesStatistics.cs | 157 ++++++++++ .../Migrations/ServerContextModelSnapshot.cs | 16 +- Server.Host/Controllers/RoomController.cs | 10 +- Server.Host/Controllers/RoundController.cs | 6 +- .../Controllers/StatisticsController.cs | 39 ++- Server.Host/Extensions/ServiceExtension.cs | 4 +- 11 files changed, 546 insertions(+), 42 deletions(-) create mode 100644 Server.Bll/Services/Interfaces/IStatisticsService.cs create mode 100644 Server.Bll/Services/StatisticsService.cs create mode 100644 Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs create mode 100644 Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 0aee6ab..9c77cc4 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -7,6 +7,7 @@ + diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs new file mode 100644 index 0000000..e8b4050 --- /dev/null +++ b/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Server.Bll.Models; + +namespace Server.Bll.Services.Interfaces +{ + public interface IStatisticsService + { + Task> GetAllStatistics(); + Task GetPersonalStatistics(int userId); + } +} \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs new file mode 100644 index 0000000..42edd63 --- /dev/null +++ b/Server.Bll/Services/StatisticsService.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; +using Server.Dal.Context; + +namespace Server.Bll.Services +{ + public class StatisticsService : IStatisticsService + { + private readonly ServerContext _repository; + + public StatisticsService(ServerContext repository) + { + _repository = repository; + } + + public async Task> GetAllStatistics() + { + return await _repository.StatisticsEnumerable.ProjectToType().ToArrayAsync(); + } + + public async Task GetPersonalStatistics(int userId) + { + var statistics = await _repository.StatisticsEnumerable + .Include(x=>x.Account).FirstOrDefaultAsync(x=>x.Id == userId); + return statistics.Adapt(); + } + } +} \ No newline at end of file diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index 2b62dc6..48d4ba7 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -28,23 +28,23 @@ public class Statistics /// /// Total amount of Wins /// - public int? Wins { get; set; } + public int Wins { get; set; } /// /// Total amount of Loses /// - public int? Loss { get; set; } + public int Loss { get; set; } /// /// Total amount of Draws. OBSOLETE /// - public int? Draws { get; set; } + public int Draws { get; set; } /// /// Ratio Wins to Losses. Win/Loss * 100 /// - public double? WinLossRatio { get; set; } + public double WinLossRatio { get; set; } /// /// Ratio for the last 7 days @@ -54,22 +54,22 @@ public class Statistics /// /// Times used rock /// - public int? UsedRock { get; set; } + public int UsedRock { get; set; } /// /// Times used Paper /// - public int? UsedPaper { get; set; } + public int UsedPaper { get; set; } /// /// Times used Scissors /// - public int? UsedScissors { get; set; } + public int UsedScissors { get; set; } /// /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// - public int? Score { get; set; } + public int Score { get; set; } } } \ No newline at end of file diff --git a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs new file mode 100644 index 0000000..a5c18d5 --- /dev/null +++ b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs @@ -0,0 +1,295 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210715210016_DeletedNullablesStatistics")] + partial class DeletedNullablesStatistics + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs new file mode 100644 index 0000000..79e7be0 --- /dev/null +++ b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs @@ -0,0 +1,157 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class DeletedNullablesStatistics : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Wins", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "WinLossRatio", + table: "Statistics", + type: "REAL", + nullable: false, + defaultValue: 0.0, + oldClrType: typeof(double), + oldType: "REAL", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "UsedScissors", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "UsedRock", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "UsedPaper", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Score", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Loss", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Draws", + table: "Statistics", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Wins", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "WinLossRatio", + table: "Statistics", + type: "REAL", + nullable: true, + oldClrType: typeof(double), + oldType: "REAL"); + + migrationBuilder.AlterColumn( + name: "UsedScissors", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "UsedRock", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "UsedPaper", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "Score", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "Loss", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AlterColumn( + name: "Draws", + table: "Statistics", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index 35bc598..a8a8aae 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -157,31 +157,31 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("AccountId") .HasColumnType("INTEGER"); - b.Property("Draws") + b.Property("Draws") .HasColumnType("INTEGER"); - b.Property("Loss") + b.Property("Loss") .HasColumnType("INTEGER"); - b.Property("Score") + b.Property("Score") .HasColumnType("INTEGER"); b.Property("TimeSpent") .HasColumnType("TEXT"); - b.Property("UsedPaper") + b.Property("UsedPaper") .HasColumnType("INTEGER"); - b.Property("UsedRock") + b.Property("UsedRock") .HasColumnType("INTEGER"); - b.Property("UsedScissors") + b.Property("UsedScissors") .HasColumnType("INTEGER"); - b.Property("WinLossRatio") + b.Property("WinLossRatio") .HasColumnType("REAL"); - b.Property("Wins") + b.Property("Wins") .HasColumnType("INTEGER"); b.HasKey("Id"); diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 3e1918d..c277188 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -12,7 +12,7 @@ namespace Server.Controllers { [ApiController] - [Route("[controller]/[action]")] + [Route("api/v1/room/")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RoomController : ControllerBase @@ -28,7 +28,7 @@ public RoomController(IRoomService roomService, _applicationUser = applicationUser; } - [HttpPost] + [HttpPost("create")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] public async Task CreateRoom() @@ -40,7 +40,7 @@ public async Task CreateRoom() exception => BadRequest(exception)); } - [HttpPost] + [HttpPost("join")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task JoinRoom([FromQuery] string roomCode) @@ -51,7 +51,7 @@ public async Task JoinRoom([FromQuery] string roomCode) exception => BadRequest(exception)); } - [HttpGet] + [HttpGet("update")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateRoom([FromBody] RoomModel roomModel) @@ -65,7 +65,7 @@ public async Task UpdateRoom([FromBody] RoomModel roomModel) }; } - [HttpDelete] + [HttpDelete("delete")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task DeleteRoom([FromQuery] int roomId) { diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index e93cd6c..0fcd11f 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -8,7 +8,7 @@ namespace Server.Controllers { [ApiController] - [Route("[controller]/[action]")] + [Route("api/v1/round")] [Produces(MediaTypeNames.Application.Json)] public class RoundController:ControllerBase { @@ -18,7 +18,7 @@ public RoundController( { _logger = logger; } - [HttpGet] + [HttpGet("get")] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task GetRound() @@ -26,7 +26,7 @@ public async Task GetRound() throw new NotImplementedException(); } - [HttpGet] + [HttpGet("update")] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateCurrentRound() diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 2b53762..29ee21d 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -1,41 +1,46 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; +using Server.Authentication.Models.Interfaces; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; using Server.Contracts; namespace Server.Controllers { [ApiController] - [Route("[controller]/[action]")] + [Route("api/v1/stats")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class StatisticsController : ControllerBase { - private readonly ILogger _logger; - - - public StatisticsController( - ILogger logger) + private readonly IStatisticsService _statisticsService; + private readonly IApplicationUser _applicationUser; + private int UserId => _applicationUser.Id; + public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) { - _logger = logger; + _statisticsService = statisticsService; + _applicationUser = applicationUser; } - [HttpGet] - [Route("overallStatistics")] + [HttpGet("all")] [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public ActionResult> GetOverallStatistics() + public async Task> GetOverallStatistics() { - throw new NotImplementedException(); + return await _statisticsService.GetAllStatistics(); } - [HttpGet] + [HttpGet("personal")] + [Authorize] //[ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public ActionResult GetPersonalStatistics() + public async Task GetPersonalStatistics() { - throw new NotImplementedException(); + return Ok(await _statisticsService.GetPersonalStatistics(UserId)); } } } \ No newline at end of file diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index 1496194..ff0a920 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -11,7 +11,9 @@ public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - service.AddTransient() + service + .AddTransient() + .AddTransient() .AddHostedService(); service.AddHttpContextAccessor(); From 7072f33834f2db7f6193f1497439f1ee78697648 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 16 Jul 2021 16:25:28 +0300 Subject: [PATCH 19/56] Update --- Server.Bll/Exceptions/CustomException.cs | 20 ++ Server.Bll/Exceptions/ExceptionTemplates.cs | 10 + Server.Bll/Exceptions/RoomException.cs | 14 - .../HostedServices/CleanerHostedService.cs | 16 +- Server.Bll/Mapper/RoomMapper.cs | 59 ---- Server.Bll/Models/RoundModel.cs | 8 +- .../Services/Interfaces/IHostedRoomService.cs | 11 + .../Services/Interfaces/IRoomService.cs | 6 +- .../Services/Interfaces/IRoundService.cs | 7 +- Server.Bll/Services/RoomService.cs | 125 +++++--- Server.Bll/Services/RoundService.cs | 103 ++++++ Server.Dal/Context/ServerContext.cs | 8 +- Server.Dal/Entities/RoomPlayers.cs | 4 - Server.Dal/Entities/Round.cs | 8 +- ...16120124_MigratedFieldsToRound.Designer.cs | 298 +++++++++++++++++ .../20210716120124_MigratedFieldsToRound.cs | 68 ++++ ...10716131403_NewFluentAssertion.Designer.cs | 299 ++++++++++++++++++ .../20210716131403_NewFluentAssertion.cs | 55 ++++ .../Migrations/ServerContextModelSnapshot.cs | 24 +- Server.Host/Controllers/RoomController.cs | 20 +- Server.Host/Controllers/RoundController.cs | 51 ++- Server.Host/Extensions/ServiceExtension.cs | 4 + 22 files changed, 1041 insertions(+), 177 deletions(-) create mode 100644 Server.Bll/Exceptions/CustomException.cs delete mode 100644 Server.Bll/Exceptions/RoomException.cs delete mode 100644 Server.Bll/Mapper/RoomMapper.cs create mode 100644 Server.Bll/Services/Interfaces/IHostedRoomService.cs create mode 100644 Server.Bll/Services/RoundService.cs create mode 100644 Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs create mode 100644 Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs create mode 100644 Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs create mode 100644 Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs diff --git a/Server.Bll/Exceptions/CustomException.cs b/Server.Bll/Exceptions/CustomException.cs new file mode 100644 index 0000000..be02c96 --- /dev/null +++ b/Server.Bll/Exceptions/CustomException.cs @@ -0,0 +1,20 @@ +namespace Server.Bll.Exceptions +{ + public readonly struct CustomException + { + public int Code { get;} + public string Message { get; } + + public CustomException(string template, int code = 400) + { + Message = template; + Code = code; + } + + public CustomException(string template, string customObject, int code = 400) + { + Message = string.Format(template, customObject); + Code = code; + } + } +} \ No newline at end of file diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index ee0a915..2d768b1 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -2,10 +2,20 @@ namespace Server.Bll.Exceptions { public static class ExceptionTemplates { + // GENERAL EXCEPTION MESSAGES public const string Unknown = "Unknown error occured. Please try again"; + public const string NotAllowed = "You are not allowed to do this."; + + // ROOM EXCEPTIONS public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; public const string RoomNotExists = "Room does not exist."; public const string RoomFull = "This room is full."; public const string AlreadyInRoom = "You are already in room."; + public const string RoomNotFull = "Room is not full."; + public const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; + + // ROUND EXCEPTIONS + public const string RoundAlreadyCreated = "Round is already creaded."; + public const string RoundNotFound = "Round with id \"{0}\" is not found"; } } \ No newline at end of file diff --git a/Server.Bll/Exceptions/RoomException.cs b/Server.Bll/Exceptions/RoomException.cs deleted file mode 100644 index 825098b..0000000 --- a/Server.Bll/Exceptions/RoomException.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Server.Bll.Exceptions -{ - public readonly struct RoomException - { - public int Code { get;} - public string Message { get; } - - public RoomException(string template, int code) - { - Message = template; - Code = code; - } - } -} \ No newline at end of file diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index 9c64aa6..e47b9ba 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Server.Bll.Services; +using Server.Bll.Services.Interfaces; namespace Server.Bll.HostedServices { @@ -27,23 +28,24 @@ public CleanerHostedService( public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer( - GenerateDailyQuests, + CleanJunk, _serviceProvider, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10)); return Task.CompletedTask; } - private async void GenerateDailyQuests(object state) + private async void CleanJunk(object state) { - _logger.LogInformation("Starting Cleaner service"); + _logger.LogInformation("Starting Cleaning."); var factory = (IServiceScopeFactory) state; using var scope = factory.CreateScope(); var roomService = scope.ServiceProvider.GetRequiredService(); - var rooms = await roomService.GetRoomsByDate(TimeSpan.FromMinutes(5)); - if (rooms is not {Length: > 0}) return; - await roomService.RemoveRoomRange(rooms); - _logger.LogInformation("Cleaned {0} entities of room",rooms.Length); + + var rooms = await roomService + .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); + + _logger.LogInformation("Cleaned {0} entities",rooms); } public Task StopAsync(CancellationToken cancellationToken) diff --git a/Server.Bll/Mapper/RoomMapper.cs b/Server.Bll/Mapper/RoomMapper.cs deleted file mode 100644 index c2c3b28..0000000 --- a/Server.Bll/Mapper/RoomMapper.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Server.Bll.Models; -using Server.Dal.Entities; - -namespace Server.Bll.Mapper -{ - public static class RoomMapper - { - public static RoomModel ToRoomModel(this Room room) - { - var roomPlayersModel = new RoomPlayersModel(); - var firstPlayerModel = new AccountModel(); - var secondPlayerModel = new AccountModel(); - if(room == null) - return null; - if (room.RoomPlayers == null) - return new RoomModel - { - Id = room.Id, - RoundId = room.RoundId, - RoomCode = room.RoomCode, - RoomPlayers = roomPlayersModel, - IsPrivate = room.IsPrivate, - IsReady = room.IsReady, - IsFull = room.IsFull, - CreationTime = new DateTime(room.CreationTimeTicks), - IsRoundEnded = room.IsRoundEnded, - }; - if (room.RoomPlayers.FirstPlayer != null) - { - firstPlayerModel.Login = room.RoomPlayers.FirstPlayer.Login; - } - if (room.RoomPlayers.SecondPlayer != null) - { - firstPlayerModel.Login = room.RoomPlayers.SecondPlayer.Login; - } - roomPlayersModel = new RoomPlayersModel - { - FirstPlayer = firstPlayerModel, - SecondPlayer = secondPlayerModel, - FirstPlayerMove = room.RoomPlayers.FirstPlayerMove, - SecondPlayerMove = room.RoomPlayers.SecondPlayerMove - }; - - return new RoomModel - { - Id = room.Id, - RoundId = room.RoundId, - RoomCode = room.RoomCode, - RoomPlayers = roomPlayersModel, - IsPrivate = room.IsPrivate, - IsReady = room.IsReady, - IsFull = room.IsFull, - CreationTime = new DateTime(room.CreationTimeTicks), - IsRoundEnded = room.IsRoundEnded, - }; - } - } -} \ No newline at end of file diff --git a/Server.Bll/Models/RoundModel.cs b/Server.Bll/Models/RoundModel.cs index 1de5bf3..fd7514c 100644 --- a/Server.Bll/Models/RoundModel.cs +++ b/Server.Bll/Models/RoundModel.cs @@ -4,14 +4,12 @@ namespace Server.Bll.Models { public class RoundModel { - public RoomPlayersModel RoomPlayers { get; set; } - + public int Id { get; set; } public bool IsFinished { get; set; } - public DateTimeOffset TimeFinished { get; set; } - public AccountModel Winner { get; set; } - public AccountModel Loser { get; set; } + public int FirstPlayerMove { get; set; } + public int SecondPlayerMove { get; set; } } } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IHostedRoomService.cs b/Server.Bll/Services/Interfaces/IHostedRoomService.cs new file mode 100644 index 0000000..0f2f848 --- /dev/null +++ b/Server.Bll/Services/Interfaces/IHostedRoomService.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using Server.Dal.Entities; + +namespace Server.Bll.Services.Interfaces +{ + public interface IHostedRoomService + { + Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); + } +} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 9905ee3..a20a267 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -8,9 +8,9 @@ namespace Server.Bll.Services.Interfaces { public interface IRoomService { - Task> CreateRoom(int userId, bool isPrivate = false); - Task> JoinRoom(int userId, string roomCode); - Task> GetRoom(int roomId); + Task> CreateRoom(int userId, bool isPrivate = false); + Task> JoinRoom(int userId, bool isPrivate, string roomCode); + Task> GetRoom(int roomId); Task UpdateRoom(RoomModel room); Task DeleteRoom(int userId, int roomId); } diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index 452b132..55d9839 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -1,13 +1,14 @@ using System.Threading.Tasks; using Server.Bll.Models; +using OneOf; +using Server.Bll.Exceptions; namespace Server.Bll.Services.Interfaces { public interface IRoundService { - Task CreateRoundAsync(); + Task> CreateRoundAsync(int userId, int roomId); Task MakeMoveAsync(); - Task UpdateRoundAsync(); - Task UpdateRound(); + Task> UpdateRoundAsync(int userId, RoundModel roundModel); } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index fd78343..062743a 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -1,12 +1,10 @@ using System; using System.Linq; -using System.Net; using System.Threading.Tasks; using Mapster; using Microsoft.EntityFrameworkCore; using OneOf; using Server.Bll.Exceptions; -using Server.Bll.Mapper; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using Server.Dal.Context; @@ -18,19 +16,19 @@ public class RoomService : IRoomService, IHostedRoomService { private readonly DbSet _rooms; private readonly ServerContext _repository; - + public RoomService(ServerContext repository) { _repository = repository; _rooms = _repository.Rooms; } - public async Task> CreateRoom(int userId, bool isPrivate = false) + public async Task> CreateRoom(int userId, bool isPrivate = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId || x.SecondPlayerId == userId); if (doesRoomExist != null) - return new RoomException(ExceptionTemplates.TwinkRoom, (int) HttpStatusCode.BadRequest); + return new CustomException(ExceptionTemplates.TwinkRoom); var room = new Room { @@ -58,22 +56,64 @@ public async Task> CreateRoom(int userId, bool i _rooms.Update(room); await _repository.SaveChangesAsync(); - return room.ToRoomModel(); + return room.Adapt(); } - public async Task> JoinRoom(int userId, string roomCode) + public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) { + if (isPrivate) + { + var randomRoom = await _rooms + .Include(x => x.RoomPlayers) + .Where(x => !x.IsFull).FirstOrDefaultAsync(); + if (randomRoom is null) + return new CustomException(ExceptionTemplates.NoAvailableRooms); + + if (randomRoom.RoomPlayers.FirstPlayerId == userId || randomRoom.RoomPlayers.SecondPlayerId == userId) + return new CustomException(ExceptionTemplates.AlreadyInRoom); + + if (randomRoom.RoomPlayers.FirstPlayerId is 0 or null) + randomRoom.RoomPlayers.FirstPlayerId = userId; + if (randomRoom.RoomPlayers.SecondPlayerId is 0 or null) + randomRoom.RoomPlayers.SecondPlayerId = userId; + + if ((randomRoom.RoomPlayers.FirstPlayerId != 0 || + randomRoom.RoomPlayers.FirstPlayerId != null) && + (randomRoom.RoomPlayers.SecondPlayerId != 0 || + randomRoom.RoomPlayers.SecondPlayerId != null)) + { + randomRoom.IsFull = true; + randomRoom.IsReady = true; + + var round = new Round + { + Id = 0, + RoomPlayersId = randomRoom.RoomPlayers.Id, + FirstPlayerMove = 0, + SecondPlayerMove = 0, + LastMoveTicks = DateTimeOffset.Now.Ticks, + IsFinished = false + }; + await _repository.Rounds.AddAsync(round); + + randomRoom.RoundId = round.Id; + randomRoom.RoomPlayers.RoundId = round.Id; + } + _rooms.Update(randomRoom); + await _repository.SaveChangesAsync(); + return randomRoom.Adapt(); + } var foundRoom = await _rooms .Include(x=>x.RoomPlayers). FirstOrDefaultAsync(x => x.RoomCode == roomCode); if (foundRoom == null) - return new RoomException(ExceptionTemplates.RoomNotExists, 400); + return new CustomException(ExceptionTemplates.RoomNotExists); if (foundRoom.RoomPlayers.FirstPlayerId == userId || foundRoom.RoomPlayers.SecondPlayerId == userId) - return new RoomException(ExceptionTemplates.AlreadyInRoom, 400); + return new CustomException(ExceptionTemplates.AlreadyInRoom); if (foundRoom.RoomPlayers.FirstPlayerId != 0 && foundRoom.RoomPlayers.SecondPlayerId != 0) - return new RoomException(ExceptionTemplates.RoomFull, 400); + return new CustomException(ExceptionTemplates.RoomFull); if (foundRoom.RoomPlayers.FirstPlayerId != 0) { @@ -85,7 +125,7 @@ public async Task> JoinRoom(int userId, string r } if (foundRoom.RoomPlayers.SecondPlayerId == 0) - return new RoomException(ExceptionTemplates.Unknown, 400); + return new CustomException(ExceptionTemplates.Unknown); foundRoom.RoomPlayers.SecondPlayerId = userId; _rooms.Update(foundRoom); @@ -94,13 +134,13 @@ public async Task> JoinRoom(int userId, string r return foundRoom.Adapt(); } - public async Task> GetRoom(int roomId) + public async Task> GetRoom(int roomId) { var room = await _rooms.FindAsync(roomId); return room != null ? room.Adapt() - : new RoomException(ExceptionTemplates.RoomNotExists, 400); + : new CustomException(ExceptionTemplates.RoomNotExists); } public async Task UpdateRoom(RoomModel room) @@ -114,10 +154,11 @@ public async Task> GetRoom(int roomId) thisRoom.IsPrivate = updatedRoom.IsPrivate; thisRoom.IsReady = updatedRoom.IsReady; thisRoom.RoundId = updatedRoom.RoundId; - - return _repository.Entry(thisRoom).Properties.Any(x => x.IsModified) - ? 400 - : 200; + + if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) return 400; + _repository.Update(thisRoom); + return 200; + } public async Task DeleteRoom(int userId, int roomId) @@ -139,44 +180,30 @@ public async Task> GetRoom(int roomId) } /// - /// ONLY TO BE USED WITH I HOSTED SERVICE. REMOVES RANGE OF ROOMS - /// - /// - /// - [Obsolete(message:"Should be carefully used")] - public async Task RemoveRoomRange(Room[] rooms) - { - try - { - _rooms.RemoveRange(rooms); - - await _repository.SaveChangesAsync(); - return true; - } - catch (Exception) - { - return false; - } - } - /// - /// Only to use with IHOSTEDSERVICE! + /// Only to use with I HOSTED SERVICE! /// - /// + /// + /// /// [Obsolete(message:"Should be carefully used")] - public async Task GetRoomsByDate(TimeSpan timeSpan) + public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) { var currentDate = DateTimeOffset.Now.Ticks; - return await _rooms - .Where(x => x.CreationTimeTicks + timeSpan.Ticks < currentDate - && x.RoundId == null) + var rooms = await _rooms + .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) .ToArrayAsync(); - } - } + + /*var allRound = await _repository.Rounds + .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) + .ToArrayAsync();*/ + + _rooms.RemoveRange(rooms); + //_repository.Rounds.RemoveRange(allRound); + + await _repository.SaveChangesAsync(); - public interface IHostedRoomService - { - Task GetRoomsByDate(TimeSpan timeSpan); - Task RemoveRoomRange(Room[] rooms); + return rooms.Length; //+ allRound.Length; + + } } } \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs new file mode 100644 index 0000000..25feeaa --- /dev/null +++ b/Server.Bll/Services/RoundService.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Server.Bll.Exceptions; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; +using Server.Dal.Context; +using OneOf; +using Server.Dal.Entities; + +namespace Server.Bll.Services +{ + public class RoundService : IRoundService + { + private readonly ServerContext _serverContext; + + public RoundService(ServerContext serverContext) + { + _serverContext = serverContext; + } + public async Task> CreateRoundAsync(int userId, int roomId) + { + var foundRoom = await _serverContext + .Rooms + .Include(x => x.RoomPlayers) + .FirstOrDefaultAsync(x => x.Id == roomId); + if (foundRoom is null) + return new CustomException(ExceptionTemplates.RoomNotExists); + if (foundRoom.RoomPlayers.FirstPlayerId != userId) + { + if(foundRoom.RoomPlayers.SecondPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); + + } + if (foundRoom.RoomPlayers.SecondPlayerId != userId) + { + if(foundRoom.RoomPlayers.FirstPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); + } + + if (!foundRoom.IsFull) + return new CustomException(ExceptionTemplates.RoomNotFull); + + var newRound = new Round + { + RoomPlayersId = foundRoom.RoomPlayers.Id, + FirstPlayerMove = 0, + SecondPlayerMove = 0, + LastMoveTicks = DateTimeOffset.Now.Ticks, + TimeFinishedTicks = 0, + IsFinished = false + }; + + await _serverContext.AddAsync(newRound); + + foundRoom.RoundId = newRound.Id; + foundRoom.RoomPlayers.RoundId = newRound.Id; + await _serverContext.SaveChangesAsync(); + + return newRound.Adapt(); + } + + [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] + public Task MakeMoveAsync() + { + throw new NotImplementedException(); + } + + public async Task> UpdateRoundAsync(int userId, RoundModel roundModel) + { + var thisRound = await _serverContext + .Rounds + .Include(x => x.RoomPlayers) + .ThenInclude(x=>x.Room) + .FirstOrDefaultAsync(x => x.Id == roundModel.Id); + + if(thisRound is null) + return new CustomException(ExceptionTemplates.RoundNotFound, roundModel.Id); + if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); + + var incomeRound = roundModel.Adapt(); + thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; + thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; + thisRound.LastMoveTicks = incomeRound.LastMoveTicks; + + if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) + { + thisRound.IsFinished = true; + thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; + } + + if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) + return new CustomException(ExceptionTemplates.NotAllowed); + + _serverContext.Update(thisRound); + await _serverContext.SaveChangesAsync(); + return thisRound.Adapt(); + } + } +} \ No newline at end of file diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index 4513500..0301171 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -37,8 +37,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany(user => user.SecondPlayer) .HasForeignKey(invite => invite.SecondPlayerId) .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(x => x.RoomPlayers) + .WithOne() + .OnDelete(DeleteBehavior.NoAction); + } } - - } \ No newline at end of file diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index 144b3f6..f3b2210 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -12,19 +12,15 @@ public class RoomPlayers [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public int RoomId { get; set; } - [ForeignKey("RoomId")] public virtual Room Room { get; set; } public int? FirstPlayerId { get; set; } [ForeignKey("FirstPlayerId")] public virtual Account FirstPlayer { get; set; } - public int FirstPlayerMove { get; set; } public int? SecondPlayerId { get; set; } [ForeignKey("SecondPlayerId")] public virtual Account SecondPlayer { get; set; } - public int SecondPlayerMove { get; set; } public int? RoundId { get; set; } - [ForeignKey("RoundId")] public virtual Round Round { get; set; } diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 0332333..dc91115 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -10,20 +10,18 @@ public class Round [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; init; } - public int RoomPlayersId { get; set; } - [ForeignKey("RoomPlayersId")] public virtual RoomPlayers RoomPlayers { get; set; } - + public int FirstPlayerMove { get; set; } + public int SecondPlayerMove { get; set; } public int? WinnerId { get; set; } [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } - public int? LoserId { get; set; } [ForeignKey("LoserId")] public virtual Account Loser { get; set; } - + public long LastMoveTicks { get; set; } public long TimeFinishedTicks { get; set; } public bool IsFinished { get; set; } diff --git a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs new file mode 100644 index 0000000..a11bc6a --- /dev/null +++ b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs @@ -0,0 +1,298 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210716120124_MigratedFieldsToRound")] + partial class MigratedFieldsToRound + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LastMoveTicks") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId"); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithMany() + .HasForeignKey("RoomPlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs new file mode 100644 index 0000000..fc54505 --- /dev/null +++ b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class MigratedFieldsToRound : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FirstPlayerMove", + table: "RoomPlayers"); + + migrationBuilder.DropColumn( + name: "SecondPlayerMove", + table: "RoomPlayers"); + + migrationBuilder.AddColumn( + name: "FirstPlayerMove", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "LastMoveTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "SecondPlayerMove", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FirstPlayerMove", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "LastMoveTicks", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "SecondPlayerMove", + table: "Rounds"); + + migrationBuilder.AddColumn( + name: "FirstPlayerMove", + table: "RoomPlayers", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "SecondPlayerMove", + table: "RoomPlayers", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs new file mode 100644 index 0000000..6e622f5 --- /dev/null +++ b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs @@ -0,0 +1,299 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210716131403_NewFluentAssertion")] + partial class NewFluentAssertion + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("IsRoundEnded") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LastMoveTicks") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("Round"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs new file mode 100644 index 0000000..471e85f --- /dev/null +++ b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class NewFluentAssertion : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds"); + + migrationBuilder.DropIndex( + name: "IX_Rounds_RoomPlayersId", + table: "Rounds"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId", + principalTable: "RoomPlayers", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds"); + + migrationBuilder.DropIndex( + name: "IX_Rounds_RoomPlayersId", + table: "Rounds"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId"); + + migrationBuilder.AddForeignKey( + name: "FK_Rounds_RoomPlayers_RoomPlayersId", + table: "Rounds", + column: "RoomPlayersId", + principalTable: "RoomPlayers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index a8a8aae..bdb168b 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -87,9 +87,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FirstPlayerId") .HasColumnType("INTEGER"); - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - b.Property("RoomId") .HasColumnType("INTEGER"); @@ -99,9 +96,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SecondPlayerId") .HasColumnType("INTEGER"); - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - b.HasKey("Id"); b.HasIndex("FirstPlayerId"); @@ -122,15 +116,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + b.Property("IsFinished") .HasColumnType("INTEGER"); + b.Property("LastMoveTicks") + .HasColumnType("INTEGER"); + b.Property("LoserId") .HasColumnType("INTEGER"); b.Property("RoomPlayersId") .HasColumnType("INTEGER"); + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + b.Property("TimeFinishedTicks") .HasColumnType("INTEGER"); @@ -141,7 +144,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LoserId"); - b.HasIndex("RoomPlayersId"); + b.HasIndex("RoomPlayersId") + .IsUnique(); b.HasIndex("WinnerId"); @@ -254,9 +258,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("LoserId"); b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) + .WithOne() + .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") + .OnDelete(DeleteBehavior.NoAction) .IsRequired(); b.HasOne("Server.Dal.Entities.Account", "Winner") diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index c277188..4771b35 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -31,21 +31,29 @@ public RoomController(IRoomService roomService, [HttpPost("create")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task CreateRoom() + public async Task CreateRoom([FromQuery] bool isPrivate) { - var newRoom = await _roomService.CreateRoom(UserId); + var newRoom = await _roomService.CreateRoom(UserId, isPrivate); return newRoom.Match( Ok, exception => BadRequest(exception)); } - - [HttpPost("join")] + + [HttpPost("join/public")] + public async Task JoinPublicRoom() + { + var result = await _roomService.JoinRoom(UserId, true,null); + return result.Match( + Ok, + exception => BadRequest(exception)); + } + [HttpPost("join/private")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task JoinRoom([FromQuery] string roomCode) + public async Task JoinPrivateRoom([FromQuery] string roomCode) { - var result = await _roomService.JoinRoom(UserId, roomCode); + var result = await _roomService.JoinRoom(UserId, false, roomCode); return result.Match( Ok, exception => BadRequest(exception)); diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 0fcd11f..d47affd 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -2,44 +2,75 @@ using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Server.Authentication.Models.Interfaces; +using Server.Bll.Models; +using Server.Bll.Services.Interfaces; namespace Server.Controllers { + /// + /// API Round Controller + /// [ApiController] [Route("api/v1/round")] [Produces(MediaTypeNames.Application.Json)] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RoundController:ControllerBase { private readonly ILogger _logger; + private readonly IApplicationUser _applicationUser; + private readonly IRoundService _roundService; + private int UserId => _applicationUser.Id; public RoundController( - ILogger logger) + ILogger logger, + IRoundService roundService, + IApplicationUser applicationUser) { _logger = logger; + _roundService = roundService; + _applicationUser = applicationUser; } - [HttpGet("get")] + /// + /// Creates round in room + /// + /// id of the room + /// + [HttpGet("create")] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task GetRound() + public async Task CreateRound(int roomId) { - throw new NotImplementedException(); - + var result = await _roundService.CreateRoundAsync(UserId, roomId); + return result.Match( + Ok, + exception => BadRequest(exception)); } - [HttpGet("update")] + /// + /// Updates current room (Patches). + /// + /// This round model from FE or client. + /// + [HttpPatch("update")] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateCurrentRound() + public async Task UpdateCurrentRound(RoundModel roundModel) { - throw new NotImplementedException(); + var updateResult = await _roundService.UpdateRoundAsync(UserId, roundModel); + return updateResult.Match( + Ok, + exception => BadRequest(exception)); } - [HttpPatch] + /*[HttpPatch] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task MakeMove() { throw new NotImplementedException(); - } + }*/ } } diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index ff0a920..8075c63 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -2,6 +2,7 @@ using Server.Authentication.Models; using Server.Authentication.Models.Interfaces; using Server.Bll.HostedServices; +using Server.Bll.Models; using Server.Bll.Services; using Server.Bll.Services.Interfaces; @@ -23,6 +24,9 @@ public static IServiceCollection AddServices(this IServiceCollection service) .AddTransient(provider => provider.GetRequiredService()) .AddTransient(provider => provider.GetRequiredService()); + service + .AddTransient(); + return service; } From bd027d62be4d2b096eb20a25c7fa6a5b327beafe Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 17 Jul 2021 15:19:08 +0300 Subject: [PATCH 20/56] Some more tweaks --- Server.Bll/Services/RoomService.cs | 60 ++-- Server.Bll/Services/RoundService.cs | 1 - Server.Dal/Entities/Account.cs | 4 +- Server.Dal/Entities/Room.cs | 15 +- Server.Dal/Entities/RoomPlayers.cs | 5 +- ...0717121841_NewFluentAssertion1.Designer.cs | 287 ++++++++++++++++++ .../20210717121841_NewFluentAssertion1.cs | 119 ++++++++ .../Migrations/ServerContextModelSnapshot.cs | 24 +- 8 files changed, 442 insertions(+), 73 deletions(-) create mode 100644 Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs create mode 100644 Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 062743a..4747496 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -34,21 +34,17 @@ public async Task> CreateRoom(int userId, bool { IsPrivate = isPrivate, RoomCode = Guid.NewGuid().ToString("n")[..8], - IsReady = false, IsFull = false, CreationTimeTicks = DateTimeOffset.Now.Ticks, - IsRoundEnded = false, }; await _rooms.AddAsync(room); await _repository.SaveChangesAsync(); - var thisUser = await _repository.Accounts.FindAsync(userId); - var newRoomPlayers = new RoomPlayers { RoomId = room.Id, FirstPlayerId = userId, - FirstPlayer = thisUser + PlayersCount = 1 }; await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); @@ -69,36 +65,31 @@ public async Task> JoinRoom(int userId, bool i if (randomRoom is null) return new CustomException(ExceptionTemplates.NoAvailableRooms); - if (randomRoom.RoomPlayers.FirstPlayerId == userId || randomRoom.RoomPlayers.SecondPlayerId == userId) + if (randomRoom.RoomPlayers.FirstPlayerId == userId + || randomRoom.RoomPlayers.SecondPlayerId == userId) return new CustomException(ExceptionTemplates.AlreadyInRoom); - if (randomRoom.RoomPlayers.FirstPlayerId is 0 or null) - randomRoom.RoomPlayers.FirstPlayerId = userId; - if (randomRoom.RoomPlayers.SecondPlayerId is 0 or null) + if (randomRoom.RoomPlayers.PlayersCount < 2) + { randomRoom.RoomPlayers.SecondPlayerId = userId; + } + + randomRoom.IsFull = true; - if ((randomRoom.RoomPlayers.FirstPlayerId != 0 || - randomRoom.RoomPlayers.FirstPlayerId != null) && - (randomRoom.RoomPlayers.SecondPlayerId != 0 || - randomRoom.RoomPlayers.SecondPlayerId != null)) + var round = new Round { - randomRoom.IsFull = true; - randomRoom.IsReady = true; - - var round = new Round - { - Id = 0, - RoomPlayersId = randomRoom.RoomPlayers.Id, - FirstPlayerMove = 0, - SecondPlayerMove = 0, - LastMoveTicks = DateTimeOffset.Now.Ticks, - IsFinished = false - }; - await _repository.Rounds.AddAsync(round); - - randomRoom.RoundId = round.Id; - randomRoom.RoomPlayers.RoundId = round.Id; - } + Id = 0, + RoomPlayersId = randomRoom.RoomPlayers.Id, + FirstPlayerMove = 0, + SecondPlayerMove = 0, + LastMoveTicks = DateTimeOffset.Now.Ticks, + IsFinished = false + }; + + await _repository.Rounds.AddAsync(round); + + randomRoom.RoundId = round.Id; + _rooms.Update(randomRoom); await _repository.SaveChangesAsync(); return randomRoom.Adapt(); @@ -152,7 +143,6 @@ public async Task> GetRoom(int roomId) thisRoom.IsFull = updatedRoom.IsFull; thisRoom.IsPrivate = updatedRoom.IsPrivate; - thisRoom.IsReady = updatedRoom.IsReady; thisRoom.RoundId = updatedRoom.RoundId; if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) return 400; @@ -193,16 +183,16 @@ public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan ro .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) .ToArrayAsync(); - /*var allRound = await _repository.Rounds + var allRound = await _repository.Rounds .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) - .ToArrayAsync();*/ + .ToArrayAsync(); _rooms.RemoveRange(rooms); - //_repository.Rounds.RemoveRange(allRound); + _repository.Rounds.RemoveRange(allRound); await _repository.SaveChangesAsync(); - return rooms.Length; //+ allRound.Length; + return rooms.Length + allRound.Length; } } diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index 25feeaa..daed2aa 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -56,7 +56,6 @@ public async Task> CreateRoundAsync(int userId await _serverContext.AddAsync(newRound); foundRoom.RoundId = newRound.Id; - foundRoom.RoomPlayers.RoundId = newRound.Id; await _serverContext.SaveChangesAsync(); return newRound.Adapt(); diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index 12cf89f..ab4360a 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -12,7 +12,7 @@ public class Account /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } + public int Id { get; set; } /// /// Nick name of Account @@ -27,7 +27,7 @@ public class Account /// /// Statistics id, connected to this account /// - public int? StatisticsId { get; set; } + public int StatisticsId { get; set; } /// /// Linked to this player statistics diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index c31df36..6fba0fb 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -39,12 +39,7 @@ public class Room /// Flag is this room is private /// public bool IsPrivate { get; set; } - - /// - /// Flag if everyone in this room is ready - /// - public bool IsReady { get; set; } - + /// /// Flag if room is full /// @@ -53,12 +48,6 @@ public class Room /// /// Creation date. After 5 minutes of inactivity will be deleted /// - public long CreationTimeTicks { get; set; } - - /// - /// Flag is current count has ended - /// - public bool IsRoundEnded { get; set; } - + public long CreationTimeTicks { get; set; } } } diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index f3b2210..48e19e1 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -11,6 +11,7 @@ public class RoomPlayers [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + public int PlayersCount { get; set; } public int RoomId { get; set; } [ForeignKey("RoomId")] public virtual Room Room { get; set; } @@ -20,9 +21,5 @@ public class RoomPlayers public int? SecondPlayerId { get; set; } [ForeignKey("SecondPlayerId")] public virtual Account SecondPlayer { get; set; } - public int? RoundId { get; set; } - [ForeignKey("RoundId")] - public virtual Round Round { get; set; } - } } \ No newline at end of file diff --git a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs new file mode 100644 index 0000000..b038f2c --- /dev/null +++ b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs @@ -0,0 +1,287 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210717121841_NewFluentAssertion1")] + partial class NewFluentAssertion1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlayersCount") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LastMoveTicks") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs new file mode 100644 index 0000000..a5cf265 --- /dev/null +++ b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs @@ -0,0 +1,119 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class NewFluentAssertion1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + table: "Accounts"); + + migrationBuilder.DropForeignKey( + name: "FK_RoomPlayers_Rounds_RoundId", + table: "RoomPlayers"); + + migrationBuilder.DropIndex( + name: "IX_RoomPlayers_RoundId", + table: "RoomPlayers"); + + migrationBuilder.DropColumn( + name: "IsReady", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "IsRoundEnded", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "RoundId", + table: "RoomPlayers"); + + migrationBuilder.AddColumn( + name: "PlayersCount", + table: "RoomPlayers", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AlterColumn( + name: "StatisticsId", + table: "Accounts", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + table: "Accounts", + column: "StatisticsId", + principalTable: "Statistics", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + table: "Accounts"); + + migrationBuilder.DropColumn( + name: "PlayersCount", + table: "RoomPlayers"); + + migrationBuilder.AddColumn( + name: "IsReady", + table: "Rooms", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsRoundEnded", + table: "Rooms", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "RoundId", + table: "RoomPlayers", + type: "INTEGER", + nullable: true); + + migrationBuilder.AlterColumn( + name: "StatisticsId", + table: "Accounts", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_RoundId", + table: "RoomPlayers", + column: "RoundId"); + + migrationBuilder.AddForeignKey( + name: "FK_Accounts_Statistics_StatisticsId", + table: "Accounts", + column: "StatisticsId", + principalTable: "Statistics", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_RoomPlayers_Rounds_RoundId", + table: "RoomPlayers", + column: "RoundId", + principalTable: "Rounds", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index bdb168b..18d9bda 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -28,7 +28,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("StatisticsId") + b.Property("StatisticsId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -53,12 +53,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsPrivate") .HasColumnType("INTEGER"); - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - b.Property("RoomCode") .HasColumnType("TEXT"); @@ -87,10 +81,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FirstPlayerId") .HasColumnType("INTEGER"); - b.Property("RoomId") + b.Property("PlayersCount") .HasColumnType("INTEGER"); - b.Property("RoundId") + b.Property("RoomId") .HasColumnType("INTEGER"); b.Property("SecondPlayerId") @@ -103,8 +97,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoomId") .IsUnique(); - b.HasIndex("RoundId"); - b.HasIndex("SecondPlayerId"); b.ToTable("RoomPlayers"); @@ -199,7 +191,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() - .HasForeignKey("StatisticsId"); + .HasForeignKey("StatisticsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Statistics"); }); @@ -233,10 +227,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") .WithMany("SecondPlayer") .HasForeignKey("SecondPlayerId") @@ -246,8 +236,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Room"); - b.Navigation("Round"); - b.Navigation("SecondPlayer"); }); From 46901ae74ebc28a8976bf98d9d72b4bfd518650f Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Tue, 20 Jul 2021 11:56:35 +0300 Subject: [PATCH 21/56] bugfix join room --- Server.Bll/Services/RoomService.cs | 79 ++--- Server.Dal/Context/ServerContext.cs | 4 + ...0720084952_NewFluentAssertion2.Designer.cs | 287 ++++++++++++++++++ .../20210720084952_NewFluentAssertion2.cs | 17 ++ 4 files changed, 338 insertions(+), 49 deletions(-) create mode 100644 Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs create mode 100644 Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 4747496..3cc5728 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -16,17 +16,21 @@ public class RoomService : IRoomService, IHostedRoomService { private readonly DbSet _rooms; private readonly ServerContext _repository; + private readonly IRoundService _roundService; - public RoomService(ServerContext repository) + public RoomService(ServerContext repository, + IRoundService roundService) { _repository = repository; + _roundService = roundService; _rooms = _repository.Rooms; } public async Task> CreateRoom(int userId, bool isPrivate = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable - .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId || x.SecondPlayerId == userId); + .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId + || x.SecondPlayerId == userId); if (doesRoomExist != null) return new CustomException(ExceptionTemplates.TwinkRoom); @@ -57,72 +61,49 @@ public async Task> CreateRoom(int userId, bool public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) { + Room thisRoom; + if (isPrivate) { - var randomRoom = await _rooms + thisRoom = await _rooms .Include(x => x.RoomPlayers) .Where(x => !x.IsFull).FirstOrDefaultAsync(); - if (randomRoom is null) - return new CustomException(ExceptionTemplates.NoAvailableRooms); - - if (randomRoom.RoomPlayers.FirstPlayerId == userId - || randomRoom.RoomPlayers.SecondPlayerId == userId) - return new CustomException(ExceptionTemplates.AlreadyInRoom); - - if (randomRoom.RoomPlayers.PlayersCount < 2) - { - randomRoom.RoomPlayers.SecondPlayerId = userId; - } - - randomRoom.IsFull = true; - - var round = new Round - { - Id = 0, - RoomPlayersId = randomRoom.RoomPlayers.Id, - FirstPlayerMove = 0, - SecondPlayerMove = 0, - LastMoveTicks = DateTimeOffset.Now.Ticks, - IsFinished = false - }; - - await _repository.Rounds.AddAsync(round); - - randomRoom.RoundId = round.Id; - - _rooms.Update(randomRoom); - await _repository.SaveChangesAsync(); - return randomRoom.Adapt(); } - var foundRoom = await _rooms - .Include(x=>x.RoomPlayers). - FirstOrDefaultAsync(x => x.RoomCode == roomCode); - - if (foundRoom == null) + else + { + thisRoom = await _rooms + .Include(x=>x.RoomPlayers). + FirstOrDefaultAsync(x => x.RoomCode == roomCode); + } + + if (thisRoom == null) return new CustomException(ExceptionTemplates.RoomNotExists); - if (foundRoom.RoomPlayers.FirstPlayerId == userId || foundRoom.RoomPlayers.SecondPlayerId == userId) + if (thisRoom.RoomPlayers.FirstPlayerId == userId || thisRoom.RoomPlayers.SecondPlayerId == userId) return new CustomException(ExceptionTemplates.AlreadyInRoom); - if (foundRoom.RoomPlayers.FirstPlayerId != 0 && foundRoom.RoomPlayers.SecondPlayerId != 0) + if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) return new CustomException(ExceptionTemplates.RoomFull); - if (foundRoom.RoomPlayers.FirstPlayerId != 0) + if (thisRoom.RoomPlayers.FirstPlayerId != 0) { - foundRoom.RoomPlayers.FirstPlayerId = userId; - _rooms.Update(foundRoom); + thisRoom.RoomPlayers.FirstPlayerId = userId; + _rooms.Update(thisRoom); await _repository.SaveChangesAsync(); - return foundRoom.Adapt(); + return thisRoom.Adapt(); } - if (foundRoom.RoomPlayers.SecondPlayerId == 0) + if (thisRoom.RoomPlayers.SecondPlayerId == 0) return new CustomException(ExceptionTemplates.Unknown); - foundRoom.RoomPlayers.SecondPlayerId = userId; - _rooms.Update(foundRoom); + thisRoom.RoomPlayers.SecondPlayerId = userId; + if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + await _roundService.CreateRoundAsync(userId, thisRoom.Id); + + _rooms.Update(thisRoom); await _repository.SaveChangesAsync(); - return foundRoom.Adapt(); + return thisRoom.Adapt(); } public async Task> GetRoom(int roomId) diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index 0301171..68431e2 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -16,6 +16,10 @@ public ServerContext(DbContextOptions contextOptions) protected override void OnModelCreating(ModelBuilder modelBuilder) { + + modelBuilder.Entity() + .HasQueryFilter(x => !x.IsFinished); + modelBuilder.Entity() .HasOne(invite => invite.FirstPlayer) .WithMany(user => user.FirstPlayer) diff --git a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs b/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs new file mode 100644 index 0000000..39984f7 --- /dev/null +++ b/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs @@ -0,0 +1,287 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20210720084952_NewFluentAssertion2")] + partial class NewFluentAssertion2 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("StatisticsId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("StatisticsId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("RoomCode") + .HasColumnType("TEXT"); + + b.Property("RoomPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId") + .IsUnique(); + + b.HasIndex("RoundId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlayersCount") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("FirstPlayerId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("SecondPlayerId"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LastMoveTicks") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomPlayersId") + .HasColumnType("INTEGER"); + + b.Property("SecondPlayerMove") + .HasColumnType("INTEGER"); + + b.Property("TimeFinishedTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomPlayersId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + .WithMany() + .HasForeignKey("StatisticsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Server.Dal.Entities.Round", "Round") + .WithMany() + .HasForeignKey("RoundId"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + { + b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") + .WithMany("FirstPlayer") + .HasForeignKey("FirstPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne() + .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") + .WithMany("SecondPlayer") + .HasForeignKey("SecondPlayerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("FirstPlayer"); + + b.Navigation("Room"); + + b.Navigation("SecondPlayer"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") + .WithOne() + .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("RoomPlayers"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("FirstPlayer"); + + b.Navigation("SecondPlayer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs b/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs new file mode 100644 index 0000000..1e0bbec --- /dev/null +++ b/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class NewFluentAssertion2 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} From b14f191f458e1191a756fce2fa90c481f3d5e4ef Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Tue, 20 Jul 2021 15:02:20 +0300 Subject: [PATCH 22/56] Added statistics --- Client/Client.csproj | 2 + Client/Menus/StartMenu.cs | 6 +- .../Models/Interfaces/IOverallStatistics.cs | 6 +- Client/Models/Interfaces/IStatistics.cs | 70 ++-- Client/Models/Statistics.cs | 59 +++- Client/Services/StatisticsService.cs | 74 +++++ Server.Bll/Services/LongPollingService.cs | 33 ++ Server.Dal/Entities/Account.cs | 2 +- Server.Dal/Entities/RoomPlayers.cs | 4 +- .../20210713165841_NULLABLES.Designer.cs | 295 ----------------- ...8_ChangedDateTimeOffsetToTicks.Designer.cs | 295 ----------------- ...0714103428_ChangedDateTimeOffsetToTicks.cs | 58 ---- ...183223_AddedTimeChangedToRound.Designer.cs | 298 ----------------- .../20210715183223_AddedTimeChangedToRound.cs | 24 -- ...ddedTimeChangedToRoundREVERTED.Designer.cs | 295 ----------------- ...5183553_AddedTimeChangedToRoundREVERTED.cs | 24 -- ...016_DeletedNullablesStatistics.Designer.cs | 295 ----------------- ...210715210016_DeletedNullablesStatistics.cs | 157 --------- ...16120124_MigratedFieldsToRound.Designer.cs | 298 ----------------- .../20210716120124_MigratedFieldsToRound.cs | 68 ---- ...10716131403_NewFluentAssertion.Designer.cs | 299 ------------------ .../20210716131403_NewFluentAssertion.cs | 55 ---- ...0717121841_NewFluentAssertion1.Designer.cs | 287 ----------------- .../20210717121841_NewFluentAssertion1.cs | 119 ------- .../20210720084952_NewFluentAssertion2.cs | 17 - ...720112106_NewFluentAssertion2.Designer.cs} | 8 +- ... => 20210720112106_NewFluentAssertion2.cs} | 63 ++-- .../Migrations/ServerContextModelSnapshot.cs | 6 +- .../Controllers/LongPollingController.cs | 33 ++ .../Controllers/StatisticsController.cs | 1 + Server.Host/Extensions/ServiceExtension.cs | 1 + Server.Host/Extensions/SwaggerExtension.cs | 4 +- Server.Host/Program.cs | 5 +- Server.Host/Startup.cs | 1 - 34 files changed, 275 insertions(+), 2987 deletions(-) create mode 100644 Client/Services/StatisticsService.cs create mode 100644 Server.Bll/Services/LongPollingService.cs delete mode 100644 Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs delete mode 100644 Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs delete mode 100644 Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs delete mode 100644 Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs delete mode 100644 Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs delete mode 100644 Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs delete mode 100644 Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs delete mode 100644 Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs delete mode 100644 Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs delete mode 100644 Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs delete mode 100644 Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs delete mode 100644 Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs delete mode 100644 Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs delete mode 100644 Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs delete mode 100644 Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs delete mode 100644 Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs rename Server.Dal/Migrations/{20210720084952_NewFluentAssertion2.Designer.cs => 20210720112106_NewFluentAssertion2.Designer.cs} (97%) rename Server.Dal/Migrations/{20210713165841_NULLABLES.cs => 20210720112106_NewFluentAssertion2.cs} (87%) create mode 100644 Server.Host/Controllers/LongPollingController.cs diff --git a/Client/Client.csproj b/Client/Client.csproj index 8713b38..8af9724 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -6,6 +6,8 @@ + + diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs index 6f9f41d..c05113c 100644 --- a/Client/Menus/StartMenu.cs +++ b/Client/Menus/StartMenu.cs @@ -10,11 +10,13 @@ namespace Client.Menus public class StartMenu { private readonly IAccountMenu _accountMenu; + private readonly IStatisticsService _statisticsService; private string SessionId { get; set; } public StartMenu(IRequestPerformer performer) { + _statisticsService = new StatisticsService(performer); _accountMenu = new AccountMenu(performer); } @@ -75,7 +77,9 @@ private async Task Menu(CancellationToken token) } Console.Clear(); break; - case 3: + case 3: + var result = await _statisticsService.GetAllStatistics(); + await _statisticsService.PrintStatistics(result); /*var statistics = await OverallStatistics(); if(statistics == null) Console.WriteLine("No statistics so far"); diff --git a/Client/Models/Interfaces/IOverallStatistics.cs b/Client/Models/Interfaces/IOverallStatistics.cs index 54392be..3b4d7c8 100644 --- a/Client/Models/Interfaces/IOverallStatistics.cs +++ b/Client/Models/Interfaces/IOverallStatistics.cs @@ -1,9 +1,9 @@ namespace Client.Models.Interfaces { - internal interface IOverallStatistics + public interface IOverallStatistics { - string Login { get; set; } - + Account Account { get; set; } int Score { get; set; } + } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IStatistics.cs b/Client/Models/Interfaces/IStatistics.cs index e0d4f36..9317d1d 100644 --- a/Client/Models/Interfaces/IStatistics.cs +++ b/Client/Models/Interfaces/IStatistics.cs @@ -1,40 +1,56 @@ -using Newtonsoft.Json; - -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces { - internal interface IStatistics + public interface IStatistics { - [JsonProperty("Id")] - string Id { get; set; } - - [JsonProperty("UserLogin")] - string Login { get; set; } - - [JsonProperty("Wins")] + /// + /// This user account + /// + Account Account { get; set; } + /// + /// Total amount of Wins + /// int Wins { get; set; } - - [JsonProperty("Loss")] + + /// + /// Total amount of Loses + /// int Loss { get; set; } - - [JsonProperty("Draws")] + + /// + /// Total amount of Draws. OBSOLETE + /// + int Draws { get; set; } - - [JsonProperty("WinToLossRatio")] + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// double WinLossRatio { get; set; } - - [JsonProperty("TimeSpent")] + + /// + /// Ratio for the last 7 days + /// string TimeSpent { get; set; } - - [JsonProperty("UsedRock")] + + /// + /// Times used rock + /// int UsedRock { get; set; } - - [JsonProperty("UsedPaper")] + + /// + /// Times used Paper + /// int UsedPaper { get; set; } - - [JsonProperty("UsedScissors")] + + /// + /// Times used Scissors + /// int UsedScissors { get; set; } - - [JsonProperty("Score")] + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// int Score { get; set; } + } } \ No newline at end of file diff --git a/Client/Models/Statistics.cs b/Client/Models/Statistics.cs index 0b4d026..6ca0d8e 100644 --- a/Client/Models/Statistics.cs +++ b/Client/Models/Statistics.cs @@ -2,26 +2,57 @@ namespace Client.Models { - internal class Statistics : IStatistics, IOverallStatistics + public class Statistics : IStatistics, IOverallStatistics { - public string Id { get; set; } - public string Login { get; set; } + /// + /// user + /// + public Account Account { get; set; } + /// + /// Total amount of Wins + /// public int Wins { get; set; } - + + /// + /// Total amount of Loses + /// public int Loss { get; set; } - + + /// + /// Total amount of Draws. OBSOLETE + /// + public int Draws { get; set; } - + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// public double WinLossRatio { get; set; } - + + /// + /// Ratio for the last 7 days + /// public string TimeSpent { get; set; } - + + /// + /// Times used rock + /// public int UsedRock { get; set; } - + + /// + /// Times used Paper + /// public int UsedPaper { get; set; } - + + /// + /// Times used Scissors + /// public int UsedScissors { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// public int Score { get; set; } public override string ToString() @@ -32,8 +63,14 @@ public override string ToString() $"{TimeSpent}\n" + $"Times used rock: {UsedRock}\n" + $"Times used paper: {UsedPaper}\n" + - $"Times used scissors: {UsedScissors}\n" + + $"Times used scissors: {UsedScissors}\n" + $"Total score: {Score}"; } + + public string ToShortString() + { + return $"UserName: {Account.Login}" + + $"Score: {Score}"; + } } } \ No newline at end of file diff --git a/Client/Services/StatisticsService.cs b/Client/Services/StatisticsService.cs new file mode 100644 index 0000000..b6cf7cc --- /dev/null +++ b/Client/Services/StatisticsService.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Client.Models; +using Client.Models.Interfaces; +using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels.Impl; +using Mapster; +using Newtonsoft.Json; + +namespace Client.Services +{ + public class StatisticsService: IStatisticsService + { + private readonly IRequestPerformer _requestPerformer; + + public StatisticsService(IRequestPerformer requestPerformer) + { + _requestPerformer = requestPerformer; + } + + public async Task GetAllStatistics() + { + var options = new RequestOptions + { + ContentType = "none", + Address = "stats/all", + IsValid = true, + Method = RequestModels.RequestMethod.Get, + Name = "OverallStats" + }; + + var response = await _requestPerformer.PerformRequestAsync(options); + + var toConvert = JsonConvert.DeserializeObject(response.Content); + return toConvert?.Adapt(); + } + + public async Task GetPersonalStatistics(string token) + { + var options = new RequestOptions + { + Headers = new Dictionary{{"Authentication",token}}, + ContentType = "none", + Address = "stats/personal", + IsValid = true, + Method = RequestModels.RequestMethod.Get, + Name = "PersonalStats" + }; + + var response = await _requestPerformer.PerformRequestAsync(options); + + return response.Content != null ? JsonConvert.DeserializeObject(response.Content) : null; + } + + public Task PrintStatistics(IOverallStatistics[] statistics) + { + for(var i = 0; i < statistics.Length; i++) + { + TextWrite.Print($"{i+1}. User: {statistics[i].Account.Login}\n" + + $"Score: {statistics[i].Score}", ConsoleColor.White); + } + Console.Write('\n'); + return Task.CompletedTask; + } + } + + public interface IStatisticsService + { + Task GetAllStatistics(); + Task GetPersonalStatistics(string token); + Task PrintStatistics(IOverallStatistics[] statistics); + } +} \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs new file mode 100644 index 0000000..7880f28 --- /dev/null +++ b/Server.Bll/Services/LongPollingService.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Server.Bll.Services.Interfaces; +using Server.Dal.Context; + +namespace Server.Bll.Services +{ + public class LongPollingService : ILongPollingService + { + private readonly ServerContext _serverContext; + + public LongPollingService(ServerContext serverContext) + { + _serverContext = serverContext; + } + + public async Task CheckRoomState(int roomId) + { + var thisRoom = await _serverContext.Rooms.FindAsync(roomId); + return thisRoom != null; + } + + public async Task CheckRoundState(int roundId) + { + var thisRound = await _serverContext.Rounds.FindAsync(roundId); + return thisRound is {WinnerId: null}; + } + } + public interface ILongPollingService + { + Task CheckRoomState(int roomId); + Task CheckRoundState(int roundId); + } +} \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index ab4360a..1c167c9 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -27,7 +27,7 @@ public class Account /// /// Statistics id, connected to this account /// - public int StatisticsId { get; set; } + public int? StatisticsId { get; set; } /// /// Linked to this player statistics diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index 48e19e1..498ec47 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Diagnostics.CodeAnalysis; namespace Server.Dal.Entities { diff --git a/Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs b/Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs deleted file mode 100644 index bc47735..0000000 --- a/Server.Dal/Migrations/20210713165841_NULLABLES.Designer.cs +++ /dev/null @@ -1,295 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210713165841_NULLABLES")] - partial class NULLABLES - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTime") - .HasColumnType("TEXT"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinished") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs deleted file mode 100644 index ee69673..0000000 --- a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.Designer.cs +++ /dev/null @@ -1,295 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210714103428_ChangedDateTimeOffsetToTicks")] - partial class ChangedDateTimeOffsetToTicks - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs b/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs deleted file mode 100644 index 8d2aade..0000000 --- a/Server.Dal/Migrations/20210714103428_ChangedDateTimeOffsetToTicks.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class ChangedDateTimeOffsetToTicks : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "TimeFinished", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "CreationTime", - table: "Rooms"); - - migrationBuilder.AddColumn( - name: "TimeFinishedTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - - migrationBuilder.AddColumn( - name: "CreationTimeTicks", - table: "Rooms", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "TimeFinishedTicks", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "CreationTimeTicks", - table: "Rooms"); - - migrationBuilder.AddColumn( - name: "TimeFinished", - table: "Rounds", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.AddColumn( - name: "CreationTime", - table: "Rooms", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - } -} diff --git a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs deleted file mode 100644 index 03911a7..0000000 --- a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.Designer.cs +++ /dev/null @@ -1,298 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210715183223_AddedTimeChangedToRound")] - partial class AddedTimeChangedToRound - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeCreatedTicks") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs b/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs deleted file mode 100644 index 4c0f3e9..0000000 --- a/Server.Dal/Migrations/20210715183223_AddedTimeChangedToRound.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class AddedTimeChangedToRound : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "TimeCreatedTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "TimeCreatedTicks", - table: "Rounds"); - } - } -} diff --git a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs deleted file mode 100644 index 278e4b7..0000000 --- a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.Designer.cs +++ /dev/null @@ -1,295 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210715183553_AddedTimeChangedToRoundREVERTED")] - partial class AddedTimeChangedToRoundREVERTED - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs b/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs deleted file mode 100644 index a052c32..0000000 --- a/Server.Dal/Migrations/20210715183553_AddedTimeChangedToRoundREVERTED.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class AddedTimeChangedToRoundREVERTED : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "TimeCreatedTicks", - table: "Rounds"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "TimeCreatedTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - } - } -} diff --git a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs deleted file mode 100644 index a5c18d5..0000000 --- a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.Designer.cs +++ /dev/null @@ -1,295 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210715210016_DeletedNullablesStatistics")] - partial class DeletedNullablesStatistics - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs b/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs deleted file mode 100644 index 79e7be0..0000000 --- a/Server.Dal/Migrations/20210715210016_DeletedNullablesStatistics.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class DeletedNullablesStatistics : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Wins", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WinLossRatio", - table: "Statistics", - type: "REAL", - nullable: false, - defaultValue: 0.0, - oldClrType: typeof(double), - oldType: "REAL", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UsedScissors", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UsedRock", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UsedPaper", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Score", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Loss", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Draws", - table: "Statistics", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Wins", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "WinLossRatio", - table: "Statistics", - type: "REAL", - nullable: true, - oldClrType: typeof(double), - oldType: "REAL"); - - migrationBuilder.AlterColumn( - name: "UsedScissors", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "UsedRock", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "UsedPaper", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "Score", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "Loss", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.AlterColumn( - name: "Draws", - table: "Statistics", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - } - } -} diff --git a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs deleted file mode 100644 index a11bc6a..0000000 --- a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.Designer.cs +++ /dev/null @@ -1,298 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210716120124_MigratedFieldsToRound")] - partial class MigratedFieldsToRound - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LastMoveTicks") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId"); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithMany() - .HasForeignKey("RoomPlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs b/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs deleted file mode 100644 index fc54505..0000000 --- a/Server.Dal/Migrations/20210716120124_MigratedFieldsToRound.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class MigratedFieldsToRound : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FirstPlayerMove", - table: "RoomPlayers"); - - migrationBuilder.DropColumn( - name: "SecondPlayerMove", - table: "RoomPlayers"); - - migrationBuilder.AddColumn( - name: "FirstPlayerMove", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "LastMoveTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - - migrationBuilder.AddColumn( - name: "SecondPlayerMove", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FirstPlayerMove", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "LastMoveTicks", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "SecondPlayerMove", - table: "Rounds"); - - migrationBuilder.AddColumn( - name: "FirstPlayerMove", - table: "RoomPlayers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "SecondPlayerMove", - table: "RoomPlayers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - } - } -} diff --git a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs deleted file mode 100644 index 6e622f5..0000000 --- a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.Designer.cs +++ /dev/null @@ -1,299 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210716131403_NewFluentAssertion")] - partial class NewFluentAssertion - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("IsRoundEnded") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LastMoveTicks") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("Round"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs b/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs deleted file mode 100644 index 471e85f..0000000 --- a/Server.Dal/Migrations/20210716131403_NewFluentAssertion.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class NewFluentAssertion : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds"); - - migrationBuilder.DropIndex( - name: "IX_Rounds_RoomPlayersId", - table: "Rounds"); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId", - principalTable: "RoomPlayers", - principalColumn: "Id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds"); - - migrationBuilder.DropIndex( - name: "IX_Rounds_RoomPlayersId", - table: "Rounds"); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId"); - - migrationBuilder.AddForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - table: "Rounds", - column: "RoomPlayersId", - principalTable: "RoomPlayers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs deleted file mode 100644 index b038f2c..0000000 --- a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.Designer.cs +++ /dev/null @@ -1,287 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20210717121841_NewFluentAssertion1")] - partial class NewFluentAssertion1 - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("StatisticsId"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RoomPlayerId") - .IsUnique(); - - b.HasIndex("RoundId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerId") - .HasColumnType("INTEGER"); - - b.Property("PlayersCount") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("SecondPlayerId"); - - b.ToTable("RoomPlayers"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LastMoveTicks") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomPlayersId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") - .WithMany() - .HasForeignKey("StatisticsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() - .HasForeignKey("RoundId"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => - { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); - - b.Navigation("SecondPlayer"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("RoomPlayers"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("FirstPlayer"); - - b.Navigation("SecondPlayer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs b/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs deleted file mode 100644 index a5cf265..0000000 --- a/Server.Dal/Migrations/20210717121841_NewFluentAssertion1.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class NewFluentAssertion1 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts"); - - migrationBuilder.DropForeignKey( - name: "FK_RoomPlayers_Rounds_RoundId", - table: "RoomPlayers"); - - migrationBuilder.DropIndex( - name: "IX_RoomPlayers_RoundId", - table: "RoomPlayers"); - - migrationBuilder.DropColumn( - name: "IsReady", - table: "Rooms"); - - migrationBuilder.DropColumn( - name: "IsRoundEnded", - table: "Rooms"); - - migrationBuilder.DropColumn( - name: "RoundId", - table: "RoomPlayers"); - - migrationBuilder.AddColumn( - name: "PlayersCount", - table: "RoomPlayers", - type: "INTEGER", - nullable: false, - defaultValue: 0); - - migrationBuilder.AlterColumn( - name: "StatisticsId", - table: "Accounts", - type: "INTEGER", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "INTEGER", - oldNullable: true); - - migrationBuilder.AddForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts", - column: "StatisticsId", - principalTable: "Statistics", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts"); - - migrationBuilder.DropColumn( - name: "PlayersCount", - table: "RoomPlayers"); - - migrationBuilder.AddColumn( - name: "IsReady", - table: "Rooms", - type: "INTEGER", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "IsRoundEnded", - table: "Rooms", - type: "INTEGER", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "RoundId", - table: "RoomPlayers", - type: "INTEGER", - nullable: true); - - migrationBuilder.AlterColumn( - name: "StatisticsId", - table: "Accounts", - type: "INTEGER", - nullable: true, - oldClrType: typeof(int), - oldType: "INTEGER"); - - migrationBuilder.CreateIndex( - name: "IX_RoomPlayers_RoundId", - table: "RoomPlayers", - column: "RoundId"); - - migrationBuilder.AddForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts", - column: "StatisticsId", - principalTable: "Statistics", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_RoomPlayers_Rounds_RoundId", - table: "RoomPlayers", - column: "RoundId", - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs b/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs deleted file mode 100644 index 1e0bbec..0000000 --- a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class NewFluentAssertion2 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs b/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs similarity index 97% rename from Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs rename to Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs index 39984f7..507e14c 100644 --- a/Server.Dal/Migrations/20210720084952_NewFluentAssertion2.Designer.cs +++ b/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs @@ -9,7 +9,7 @@ namespace Server.Dal.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20210720084952_NewFluentAssertion2")] + [Migration("20210720112106_NewFluentAssertion2")] partial class NewFluentAssertion2 { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -30,7 +30,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("StatisticsId") + b.Property("StatisticsId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -193,9 +193,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() - .HasForeignKey("StatisticsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("StatisticsId"); b.Navigation("Statistics"); }); diff --git a/Server.Dal/Migrations/20210713165841_NULLABLES.cs b/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs similarity index 87% rename from Server.Dal/Migrations/20210713165841_NULLABLES.cs rename to Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs index ff412ec..3390faa 100644 --- a/Server.Dal/Migrations/20210713165841_NULLABLES.cs +++ b/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs @@ -1,9 +1,8 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace Server.Dal.Migrations { - public partial class NULLABLES : Migration + public partial class NewFluentAssertion2 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -13,12 +12,10 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), + PlayersCount = table.Column(type: "INTEGER", nullable: false), RoomId = table.Column(type: "INTEGER", nullable: false), FirstPlayerId = table.Column(type: "INTEGER", nullable: true), - FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), - SecondPlayerId = table.Column(type: "INTEGER", nullable: true), - SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), - RoundId = table.Column(type: "INTEGER", nullable: true) + SecondPlayerId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { @@ -32,10 +29,13 @@ protected override void Up(MigrationBuilder migrationBuilder) Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), RoomPlayersId = table.Column(type: "INTEGER", nullable: false), - IsFinished = table.Column(type: "INTEGER", nullable: false), - TimeFinished = table.Column(type: "TEXT", nullable: false), + FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), + SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), WinnerId = table.Column(type: "INTEGER", nullable: true), - LoserId = table.Column(type: "INTEGER", nullable: true) + LoserId = table.Column(type: "INTEGER", nullable: true), + LastMoveTicks = table.Column(type: "INTEGER", nullable: false), + TimeFinishedTicks = table.Column(type: "INTEGER", nullable: false), + IsFinished = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { @@ -44,8 +44,7 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_Rounds_RoomPlayers_RoomPlayersId", column: x => x.RoomPlayersId, principalTable: "RoomPlayers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + principalColumn: "Id"); }); migrationBuilder.CreateTable( @@ -58,10 +57,8 @@ protected override void Up(MigrationBuilder migrationBuilder) RoundId = table.Column(type: "INTEGER", nullable: true), RoomPlayerId = table.Column(type: "INTEGER", nullable: true), IsPrivate = table.Column(type: "INTEGER", nullable: false), - IsReady = table.Column(type: "INTEGER", nullable: false), IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTime = table.Column(type: "TEXT", nullable: false), - IsRoundEnded = table.Column(type: "INTEGER", nullable: false) + CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { @@ -87,15 +84,15 @@ protected override void Up(MigrationBuilder migrationBuilder) Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), AccountId = table.Column(type: "INTEGER", nullable: false), - Wins = table.Column(type: "INTEGER", nullable: true), - Loss = table.Column(type: "INTEGER", nullable: true), - Draws = table.Column(type: "INTEGER", nullable: true), - WinLossRatio = table.Column(type: "REAL", nullable: true), + Wins = table.Column(type: "INTEGER", nullable: false), + Loss = table.Column(type: "INTEGER", nullable: false), + Draws = table.Column(type: "INTEGER", nullable: false), + WinLossRatio = table.Column(type: "REAL", nullable: false), TimeSpent = table.Column(type: "TEXT", nullable: true), - UsedRock = table.Column(type: "INTEGER", nullable: true), - UsedPaper = table.Column(type: "INTEGER", nullable: true), - UsedScissors = table.Column(type: "INTEGER", nullable: true), - Score = table.Column(type: "INTEGER", nullable: true) + UsedRock = table.Column(type: "INTEGER", nullable: false), + UsedPaper = table.Column(type: "INTEGER", nullable: false), + UsedScissors = table.Column(type: "INTEGER", nullable: false), + Score = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { @@ -139,11 +136,6 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "RoomId", unique: true); - migrationBuilder.CreateIndex( - name: "IX_RoomPlayers_RoundId", - table: "RoomPlayers", - column: "RoundId"); - migrationBuilder.CreateIndex( name: "IX_RoomPlayers_SecondPlayerId", table: "RoomPlayers", @@ -168,7 +160,8 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.CreateIndex( name: "IX_Rounds_RoomPlayersId", table: "Rounds", - column: "RoomPlayersId"); + column: "RoomPlayersId", + unique: true); migrationBuilder.CreateIndex( name: "IX_Rounds_WinnerId", @@ -202,14 +195,6 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); - migrationBuilder.AddForeignKey( - name: "FK_RoomPlayers_Rounds_RoundId", - table: "RoomPlayers", - column: "RoundId", - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - migrationBuilder.AddForeignKey( name: "FK_Rounds_Accounts_LoserId", table: "Rounds", @@ -261,10 +246,6 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "FK_RoomPlayers_Rooms_RoomId", table: "RoomPlayers"); - migrationBuilder.DropForeignKey( - name: "FK_RoomPlayers_Rounds_RoundId", - table: "RoomPlayers"); - migrationBuilder.DropTable( name: "Statistics"); diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index 18d9bda..3c9da7f 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -28,7 +28,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("StatisticsId") + b.Property("StatisticsId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -191,9 +191,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Server.Dal.Entities.Statistics", "Statistics") .WithMany() - .HasForeignKey("StatisticsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("StatisticsId"); b.Navigation("Statistics"); }); diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs new file mode 100644 index 0000000..576a88f --- /dev/null +++ b/Server.Host/Controllers/LongPollingController.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Server.Bll.Services; + +namespace Server.Controllers +{ + [ApiController] + [Route("api/v1")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + public class LongPollingController : ControllerBase + { + private readonly ILongPollingService _longPollingService; + + public LongPollingController(ILongPollingService longPollingService) + { + _longPollingService = longPollingService; + } + + [HttpGet("room")] + public async Task CheckRoomState([FromQuery] int roomId) + { + return await _longPollingService.CheckRoomState(roomId); + } + + [HttpGet("round")] + public async Task CheckRoundState(int roundId) + { + return await _longPollingService.CheckRoundState(roundId); + } + } +} \ No newline at end of file diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 29ee21d..b1a3dd5 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -27,6 +27,7 @@ public StatisticsController(IStatisticsService statisticsService, IApplicationUs } [HttpGet("all")] + [AllowAnonymous] [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] public async Task> GetOverallStatistics() diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index 8075c63..86354fa 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -15,6 +15,7 @@ public static IServiceCollection AddServices(this IServiceCollection service) service .AddTransient() .AddTransient() + .AddTransient() .AddHostedService(); service.AddHttpContextAccessor(); diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index afa1f5d..46b4886 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -1,10 +1,8 @@ using System; -using System.IO; -using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; -namespace Server.Host.Extensions +namespace Server.Extensions { /// /// Swagger extension diff --git a/Server.Host/Program.cs b/Server.Host/Program.cs index b3f13ed..6672702 100644 --- a/Server.Host/Program.cs +++ b/Server.Host/Program.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; -using Server.Host; namespace Server { @@ -13,8 +12,8 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + private static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) .ConfigureLogging(loggingBuilder => { loggingBuilder.ClearProviders(); diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index 5aea1ba..11a5630 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -7,7 +7,6 @@ using Server.Authentication.Extensions; using Server.Dal.Context; using Server.Extensions; -using Server.Host.Extensions; namespace Server { From ab560fb263375ef9d004791233bf334057baa0cd Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Wed, 21 Jul 2021 18:42:16 +0300 Subject: [PATCH 23/56] update of StatisticsService.cs --- Server.Bll/Services/StatisticsService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index 42edd63..511da27 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -19,13 +19,15 @@ public StatisticsService(ServerContext repository) public async Task> GetAllStatistics() { - return await _repository.StatisticsEnumerable.ProjectToType().ToArrayAsync(); + return await _repository.StatisticsEnumerable.ProjectToType() + .ToArrayAsync(); } public async Task GetPersonalStatistics(int userId) { var statistics = await _repository.StatisticsEnumerable - .Include(x=>x.Account).FirstOrDefaultAsync(x=>x.Id == userId); + .Include(x=>x.Account) + .FirstOrDefaultAsync(x=>x.Id == userId); return statistics.Adapt(); } } From 70e1d77e4d822637ec7c2e44018f9457def5ab8f Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Wed, 21 Jul 2021 18:44:10 +0300 Subject: [PATCH 24/56] tweaks --- Server.Host/Controllers/RoundController.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index d47affd..8c2c7dd 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -1,11 +1,9 @@ -using System; -using System.Net; +using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Server.Authentication.Models.Interfaces; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -21,16 +19,13 @@ namespace Server.Controllers [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RoundController:ControllerBase { - private readonly ILogger _logger; private readonly IApplicationUser _applicationUser; private readonly IRoundService _roundService; private int UserId => _applicationUser.Id; public RoundController( - ILogger logger, IRoundService roundService, IApplicationUser applicationUser) { - _logger = logger; _roundService = roundService; _applicationUser = applicationUser; } @@ -39,7 +34,7 @@ public RoundController( /// /// id of the room /// - [HttpGet("create")] + [HttpPost("create")] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task CreateRound(int roomId) From d3c886f1dfe7b7a2a54571c2b0bebf51f5d49d72 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Wed, 21 Jul 2021 23:16:49 +0300 Subject: [PATCH 25/56] More fixing --- Client/Menus/AccountMenu.cs | 5 +- Client/Menus/IAccountMenu.cs | 2 +- Client/Menus/MainMenu.cs | 36 +++++++---- Client/Menus/StartMenu.cs | 8 ++- Client/Models/Account.cs | 3 +- Client/Models/Room.cs | 2 +- Client/Services/RoomService.cs | 62 +++++++++++++++++++ Client/Services/StatisticsService.cs | 2 +- .../Services/Interfaces/IRoomService.cs | 2 +- Server.Bll/Services/RoomService.cs | 15 +++-- Server.Host/Controllers/RoomController.cs | 8 ++- Server.Host/Controllers/RoundController.cs | 8 --- 12 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 Client/Services/RoomService.cs diff --git a/Client/Menus/AccountMenu.cs b/Client/Menus/AccountMenu.cs index fee29b2..95fbc80 100644 --- a/Client/Menus/AccountMenu.cs +++ b/Client/Menus/AccountMenu.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net; using System.Threading.Tasks; using Client.Extensions; using Client.Models; @@ -53,7 +52,7 @@ public async Task RegisterAsync() return true; } - public async Task<(string token, Account inputAccount)> LoginAsync() + public async Task<(string token, TokenModel inputAccount)> LoginAsync() { var inputAccount = new Account { @@ -75,7 +74,7 @@ public async Task RegisterAsync() if (reachedResponse.TryParseJson(out var tokenModel)) { TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); - return (tokenModel.Token, inputAccount); + return (tokenModel.Token, tokenModel); } var error = JsonConvert.DeserializeObject(reachedResponse.Content); diff --git a/Client/Menus/IAccountMenu.cs b/Client/Menus/IAccountMenu.cs index 37a41d8..50a82c0 100644 --- a/Client/Menus/IAccountMenu.cs +++ b/Client/Menus/IAccountMenu.cs @@ -6,7 +6,7 @@ namespace Client.Menus public interface IAccountMenu { Task RegisterAsync(); - Task<(string token, Account inputAccount)> LoginAsync(); + Task<(string token, TokenModel inputAccount)> LoginAsync(); Task LogoutAsync(string token); } } \ No newline at end of file diff --git a/Client/Menus/MainMenu.cs b/Client/Menus/MainMenu.cs index a92184c..1bd70c7 100644 --- a/Client/Menus/MainMenu.cs +++ b/Client/Menus/MainMenu.cs @@ -1,17 +1,24 @@ using System; using System.Threading.Tasks; -using Client.Models.Interfaces; +using Client.Models; using Client.Services; +using Client.Services.RequestProcessor; namespace Client.Menus { public class MainMenu : IMainMenu { - private readonly IAccount _playerAccount; + private readonly TokenModel _playerAccount; + private readonly IRoomService _roomService; + private readonly IStatisticsService _statisticsService; - public MainMenu(IAccount account) + public MainMenu(TokenModel account, + IRequestPerformer requestPerformer, + IStatisticsService statisticsService) { _playerAccount = account; + _statisticsService = statisticsService; + _roomService = new RoomService(_playerAccount, requestPerformer); } public async Task PlayerMenu() @@ -38,38 +45,43 @@ public async Task PlayerMenu() TextWrite.Print("Unsupported input", ConsoleColor.Red); continue; } - /*switch (playersMenuInput) + switch (playersMenuInput) { case 1: Console.Clear(); - await JoinRoomWithBot(); + //await JoinRoomWithBot(); + var room = await _roomService.CreateRoom(true, true); + if (room is null) + return; + //todo: redirect somewhere break; case 2: Console.Clear(); - await CreationRoom(); + //await CreationRoom(); break; case 3: Console.Clear(); - await JoinPrivateRoom(); + //await JoinPrivateRoom(); break; case 4: Console.Clear(); - await JoinPublicRoom(); + //await JoinPublicRoom(); break; case 5: Console.Clear(); - var statistics = await PersonalStatistics(_sessionId); + var statistics = await _statisticsService + .GetPersonalStatistics(_playerAccount.BearerToken); Console.WriteLine(statistics+"\n\nPress any key to go back."); Console.ReadKey(); break; case 6: Console.Clear(); - await Logout(); + //await Logout(); return; default: - TextWrite.PrintLineMessageWithSpecialColor("Unsupported input", ConsoleColor.Red); + TextWrite.Print("Unsupported input", ConsoleColor.Red); continue; - }*/ + } } } } diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs index c05113c..bd186c2 100644 --- a/Client/Menus/StartMenu.cs +++ b/Client/Menus/StartMenu.cs @@ -11,11 +11,13 @@ public class StartMenu { private readonly IAccountMenu _accountMenu; private readonly IStatisticsService _statisticsService; + private readonly IRequestPerformer _requestPerformer; private string SessionId { get; set; } public StartMenu(IRequestPerformer performer) { + _requestPerformer = performer; _statisticsService = new StatisticsService(performer); _accountMenu = new AccountMenu(performer); } @@ -67,13 +69,13 @@ private async Task Menu(CancellationToken token) Console.Clear(); break; case 2: - Account inputAccount; + TokenModel inputAccount; (SessionId, inputAccount) = await _accountMenu.LoginAsync(); if (!string.IsNullOrEmpty(SessionId)) { - Console.ReadKey(); Console.Clear(); - await new MainMenu(inputAccount).PlayerMenu(); + await new MainMenu(inputAccount,_requestPerformer,_statisticsService) + .PlayerMenu(); } Console.Clear(); break; diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs index 507b0aa..fc2db6e 100644 --- a/Client/Models/Account.cs +++ b/Client/Models/Account.cs @@ -15,8 +15,9 @@ public class Account : IAccount public class TokenModel { + [JsonIgnore] public string BearerToken => "Bearer " + Token; [JsonProperty("Token")] - public string Token { get; set; } + public string Token {get; set; } [JsonProperty("Login")] public string Login { get; set; } } diff --git a/Client/Models/Room.cs b/Client/Models/Room.cs index 07e93e8..18d1cd5 100644 --- a/Client/Models/Room.cs +++ b/Client/Models/Room.cs @@ -5,7 +5,7 @@ namespace Client.Models { - internal class Room : IRoom + public class Room : IRoom { [JsonProperty("RoomId")] public string RoomId { get; set; } diff --git a/Client/Services/RoomService.cs b/Client/Services/RoomService.cs new file mode 100644 index 0000000..de45a58 --- /dev/null +++ b/Client/Services/RoomService.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Client.Extensions; +using Client.Models; +using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels.Impl; +using Newtonsoft.Json; + +namespace Client.Services +{ + public class RoomService : IRoomService + { + private readonly TokenModel _tokenModel; + private readonly IRequestPerformer _requestPerformer; + + public RoomService(TokenModel tokenModel, + IRequestPerformer requestPerformer) + { + _tokenModel = tokenModel; + _requestPerformer = requestPerformer; + } + public async Task CreateRoom(bool isPrivate, bool isTraining) + { + Console.WriteLine("Trying to create a room."); + + var options = new RequestOptions + { + Address = $"room/create?isPrivate={isPrivate}", + IsValid = true, + Headers = new Dictionary + { + { + "Authorization", _tokenModel.BearerToken + }, + { + "X-Training", isTraining.ToString() + } + }, + Method = RequestModels.RequestMethod.Post, + Body = string.Empty, + Name = "Create Room" + }; + var reachedResponse = await _requestPerformer + .PerformRequestAsync(options); + + if (reachedResponse.TryParseJson(out var errorModel)) + { + TextWrite.Print(errorModel.Message, ConsoleColor.Red); + return null; + } + + var room = JsonConvert.DeserializeObject(reachedResponse.Content); + return room; + } + } + + public interface IRoomService + { + Task CreateRoom(bool isPrivate, bool isTraining); + } +} \ No newline at end of file diff --git a/Client/Services/StatisticsService.cs b/Client/Services/StatisticsService.cs index b6cf7cc..652af2b 100644 --- a/Client/Services/StatisticsService.cs +++ b/Client/Services/StatisticsService.cs @@ -40,7 +40,7 @@ public async Task GetPersonalStatistics(string token) { var options = new RequestOptions { - Headers = new Dictionary{{"Authentication",token}}, + Headers = new Dictionary{{"Authorization",token}}, ContentType = "none", Address = "stats/personal", IsValid = true, diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index a20a267..cf77a93 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -8,7 +8,7 @@ namespace Server.Bll.Services.Interfaces { public interface IRoomService { - Task> CreateRoom(int userId, bool isPrivate = false); + Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false); Task> JoinRoom(int userId, bool isPrivate, string roomCode); Task> GetRoom(int roomId); Task UpdateRoom(RoomModel room); diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 3cc5728..53b9d93 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -26,7 +26,8 @@ public RoomService(ServerContext repository, _rooms = _repository.Rooms; } - public async Task> CreateRoom(int userId, bool isPrivate = false) + public async Task> + CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId @@ -39,18 +40,22 @@ public async Task> CreateRoom(int userId, bool IsPrivate = isPrivate, RoomCode = Guid.NewGuid().ToString("n")[..8], IsFull = false, - CreationTimeTicks = DateTimeOffset.Now.Ticks, + CreationTimeTicks = DateTimeOffset.Now.Ticks }; await _rooms.AddAsync(room); await _repository.SaveChangesAsync(); - var newRoomPlayers = new RoomPlayers { RoomId = room.Id, FirstPlayerId = userId, PlayersCount = 1 }; - + if (isTraining) + { + var bot = await _repository.Accounts.FirstAsync(x => x.Login == "BOT"); + newRoomPlayers.SecondPlayerId = bot.Id; + } + await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); room.RoomPlayers = newRoomPlayers; _rooms.Update(room); @@ -58,7 +63,7 @@ public async Task> CreateRoom(int userId, bool await _repository.SaveChangesAsync(); return room.Adapt(); } - + public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) { Room thisRoom; diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 4771b35..40f3df5 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -31,9 +31,13 @@ public RoomController(IRoomService roomService, [HttpPost("create")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task CreateRoom([FromQuery] bool isPrivate) + public async Task CreateRoom([FromQuery] bool isPrivate, + [FromHeader(Name="X-Training")] bool isTraining = false) { - var newRoom = await _roomService.CreateRoom(UserId, isPrivate); + if (isTraining) + throw new NotImplementedException(); + var newRoom = await _roomService + .CreateRoom(UserId, isPrivate); return newRoom.Match( Ok, diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 8c2c7dd..1f6c186 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -59,13 +59,5 @@ public async Task UpdateCurrentRound(RoundModel roundModel) Ok, exception => BadRequest(exception)); } - - /*[HttpPatch] - //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task MakeMove() - { - throw new NotImplementedException(); - }*/ } } From 5b81d95fad852d861e6505a48f68c83a904153d7 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Wed, 21 Jul 2021 23:29:24 +0300 Subject: [PATCH 26/56] Fixed bot --- Server.Host/Controllers/RoomController.cs | 6 +++--- Server.Host/Extensions/SeedingExtension.cs | 23 ++++++++++++++++++++++ Server.Host/Startup.cs | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 Server.Host/Extensions/SeedingExtension.cs diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 40f3df5..9aeb47d 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -5,7 +5,9 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OneOf; using Server.Authentication.Models.Interfaces; +using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -34,10 +36,8 @@ public RoomController(IRoomService roomService, public async Task CreateRoom([FromQuery] bool isPrivate, [FromHeader(Name="X-Training")] bool isTraining = false) { - if (isTraining) - throw new NotImplementedException(); var newRoom = await _roomService - .CreateRoom(UserId, isPrivate); + .CreateRoom(UserId, isPrivate, isTraining); return newRoom.Match( Ok, diff --git a/Server.Host/Extensions/SeedingExtension.cs b/Server.Host/Extensions/SeedingExtension.cs new file mode 100644 index 0000000..9e8fc35 --- /dev/null +++ b/Server.Host/Extensions/SeedingExtension.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Server.Dal.Context; +using Server.Dal.Entities; + +namespace Server.Extensions +{ + public static class SeedingExtension + { + public static async Task EnsureBotCreated(this ServerContext context) + { + var bot = await context.Accounts.FirstOrDefaultAsync(x => x.Login.ToLower() == "bot"); + if (bot == null) + await context.AddAsync(new Account + { + Id = 0, + Login = "BOT", + Password = "SKJSDKBNDFB21321412UIWHFDKSJGNKSDJGN" + }); + await context.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index 11a5630..0761e74 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -38,6 +38,7 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, ServerContext serverContext) { serverContext?.Database.Migrate(); + serverContext?.EnsureBotCreated(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); From 083524ed80cf2b40233bbf3cfdd8383b2de1f78b Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 9 Jan 2022 17:48:36 +0200 Subject: [PATCH 27/56] Some tweaks --- RockPaperScissorsGame.sln | 1 + Server.Authentication/AuthOptions.cs | 2 +- .../Exceptions/UserException.cs | 8 +-- Server.Authentication/HashingBase64.cs | 5 +- .../Services/AttemptValidationService.cs | 33 +++++++----- Server.Authentication/Services/AuthService.cs | 37 +++++++++----- .../HostedServices/CleanerHostedService.cs | 5 +- .../Interfaces/ILongPollingService.cs | 10 ++++ Server.Bll/Services/LongPollingService.cs | 11 ++-- Server.Bll/Services/RoomService.cs | 50 ++++++++++++++++--- Server.Bll/Services/StatisticsService.cs | 1 + .../Extensions/DatabaseExtension.cs | 2 +- .../Extensions/SeedingExtension.cs | 5 +- Server.Host/Contracts/AccountDto.cs | 5 +- .../Contracts/Requests/LoginRequest.cs | 2 +- .../Contracts/Requests/RegisterRequest.cs | 2 +- Server.Host/Contracts/StatisticsDto.cs | 2 +- .../Contracts/ViewModels/AccountViewModel.cs | 2 +- Server.Host/Controllers/AccountController.cs | 6 +-- .../Controllers/LongPollingController.cs | 2 +- Server.Host/Controllers/RoomController.cs | 7 +-- Server.Host/Controllers/RoundController.cs | 2 +- .../Controllers/StatisticsController.cs | 4 +- Server.Host/Extensions/LoggingMiddleware.cs | 2 +- Server.Host/Extensions/ServiceExtension.cs | 3 +- Server.Host/Extensions/SwaggerExtension.cs | 2 +- Server.Host/Program.cs | 4 +- Server.Host/Properties/launchSettings.json | 2 +- Server.Host/Server.Host.csproj | 1 - Server.Host/Startup.cs | 5 +- 30 files changed, 143 insertions(+), 80 deletions(-) create mode 100644 Server.Bll/Services/Interfaces/ILongPollingService.cs rename {Server.Host => Server.Dal}/Extensions/DatabaseExtension.cs (96%) rename {Server.Host => Server.Dal}/Extensions/SeedingExtension.cs (88%) diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 060574d..37b6cce 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -50,5 +50,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {004A30E1-2903-4C9F-8D02-EEBE67C677BD} = {09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8} + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430} = {09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8} EndGlobalSection EndGlobal diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index 00687b7..7202e4c 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -43,7 +43,7 @@ public class AuthOptions /// public SymmetricSecurityKey GetSymmetricSecurityKey() { - return new(Encoding.ASCII.GetBytes(PrivateKey)); + return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey)); } } } \ No newline at end of file diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs index 36173b6..247ae85 100644 --- a/Server.Authentication/Exceptions/UserException.cs +++ b/Server.Authentication/Exceptions/UserException.cs @@ -1,5 +1,5 @@ using System; -using System.Net; +using Microsoft.AspNetCore.Http; namespace Server.Authentication.Exceptions { @@ -10,13 +10,13 @@ public readonly struct UserException public UserException(string messageType, string message, DateTimeOffset dateTimeOffset) { - Code = (int) HttpStatusCode.BadRequest; - Message = string.Format(messageType, message, dateTimeOffset); + Code = StatusCodes.Status400BadRequest; + Message = string.Format(messageType, message, dateTimeOffset.ToString("f")); } public UserException(string invalidCredentialsForUser, string loginName) { - Code = (int) HttpStatusCode.BadRequest; + Code = StatusCodes.Status400BadRequest; Message = string.Format(invalidCredentialsForUser, loginName); } } diff --git a/Server.Authentication/HashingBase64.cs b/Server.Authentication/HashingBase64.cs index 0588797..92b9048 100644 --- a/Server.Authentication/HashingBase64.cs +++ b/Server.Authentication/HashingBase64.cs @@ -7,7 +7,7 @@ public static class HashingBase64 public static string DecodeBase64(this string encodedData) { var encodedDataAsBytes - = System.Convert.FromBase64String(encodedData); + = Convert.FromBase64String(encodedData); return System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); } @@ -16,12 +16,13 @@ public static string EncodeBase64(this string decodedData) { var dataArray = System.Text.Encoding.ASCII.GetBytes(decodedData); - return System.Convert.ToBase64String(dataArray); + return Convert.ToBase64String(dataArray); } public static bool IsHashEqual(this string base64Data, string initialData) { var baseDecoded = EncodeBase64(initialData); + return base64Data.Equals(baseDecoded, StringComparison.OrdinalIgnoreCase); } } diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 4024e7e..44eda81 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; namespace Server.Authentication.Services { @@ -11,7 +9,7 @@ public class AttemptValidationService private readonly ConcurrentDictionary _failedAttempts = new(); private readonly ConcurrentDictionary _coolDownCollection = new(); - public Task InsertFailAttempt(string userId) + public bool InsertFailAttempt(string userId) { if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) { @@ -20,27 +18,36 @@ public Task InsertFailAttempt(string userId) // todo: in options _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); _failedAttempts.TryRemove(userId, out _); - return Task.CompletedTask; + return true; } } - _failedAttempts.AddOrUpdate(userId, 1, (s, i) => i + 1); + _failedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); - return Task.CompletedTask; + return true; } - public Task CountFailedAttempts(string userId) + public int? CountFailedAttempts(string userId) { - _failedAttempts.TryGetValue(userId, out var failedAttempts); - return Task.FromResult(failedAttempts); + return _failedAttempts.TryGetValue(userId, out var failedAttempts) + ? failedAttempts + : default; } - public Task IsCoolDown(string userId, out DateTimeOffset coolDownDate) + public bool IsCoolDown(string userId, out DateTimeOffset coolDownDate) { var result = _coolDownCollection.TryGetValue(userId, out coolDownDate); - if (!result) return Task.FromResult(false); - if (coolDownDate >= DateTimeOffset.Now) return Task.FromResult(true); + + if (!result) + { + return false; + } + if (coolDownDate >= DateTimeOffset.Now) + { + return true; + } _coolDownCollection.TryRemove(userId, out coolDownDate); - return Task.FromResult(false); + + return false; } } } \ No newline at end of file diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index f4a3793..9499eb9 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -24,11 +24,10 @@ internal class AuthService : IAuthService private readonly AttemptValidationService _attemptValidationService; private readonly AuthOptions _authOptions; private readonly ILogger _logger; + private readonly SemaphoreSlim _semaphore = new(1, 1); - private readonly DbSet _accounts; - public AuthService( ILogger logger, IOptions authOptions, @@ -46,22 +45,31 @@ public AuthService( public async Task> RegisterAsync(string login, string password) { - if (login == null) return new UserException(UserExceptionsTemplates.UserInvalidCredentials ,nameof(login)); - if (password == null) return new UserException(UserExceptionsTemplates.UserInvalidCredentials ,nameof(password)); + if (string.IsNullOrWhiteSpace(login)) + { + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(login)); + } + + if (string.IsNullOrEmpty(password)) + { + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(password)); + } var release = await _semaphore.WaitAsync(1000); try { if (await _accounts.CountAsync(x=>x.Login == login) > 0) - return new UserException(UserExceptionsTemplates.UserAlreadyExists,login); + { + return new UserException(UserExceptionsTemplates.UserAlreadyExists, login); + } var account = new Account { Login = login, Password = password.EncodeBase64() }; + await _accounts.AddAsync(account); - await _repository.SaveChangesAsync(); var accountStatistics = new Statistics @@ -75,7 +83,7 @@ public async Task> } catch { - _logger.LogWarning("Unable to process account for {0}", login); + _logger.LogWarning("Unable to process account for {Login}", login); return new UserException(UserExceptionsTemplates.UnknownError,string.Empty); } finally @@ -86,7 +94,6 @@ public async Task> } /// - //public async Task<(AccountOutputModel,DateTimeOffset)> public async Task> LoginAsync(string login, string password) { var userAccount = await _accounts.FirstOrDefaultAsync(x => x.Login == login); @@ -96,17 +103,22 @@ public async Task> LoginAsync(string lo return new UserException(UserExceptionsTemplates.UserNotFound,login); } - if (await _attemptValidationService.IsCoolDown(login, out var coolRequestDate)) - return new UserException(UserExceptionsTemplates.UserCoolDown,login,coolRequestDate); + if (_attemptValidationService.IsCoolDown(login, out var coolRequestDate)) + { + return new UserException(UserExceptionsTemplates.UserCoolDown, login, coolRequestDate); + } if (userAccount.Password.IsHashEqual(login)) - return new AccountOutputModel + { + return new AccountOutputModel { Token = BuildToken(userAccount), Login = userAccount.Login }; + } + + _attemptValidationService.InsertFailAttempt(login); - await _attemptValidationService.InsertFailAttempt(login); return new UserException(UserExceptionsTemplates.UserInvalidCredentials, login); } @@ -148,6 +160,7 @@ private static ClaimsIdentity GetClaimsIdentity(int userId) "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); + return claimsIdentity; } } diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index e47b9ba..d651dcd 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -37,7 +37,7 @@ public Task StartAsync(CancellationToken cancellationToken) private async void CleanJunk(object state) { - _logger.LogInformation("Starting Cleaning."); + _logger.LogInformation("Starting Cleaning"); var factory = (IServiceScopeFactory) state; using var scope = factory.CreateScope(); var roomService = scope.ServiceProvider.GetRequiredService(); @@ -45,12 +45,13 @@ private async void CleanJunk(object state) var rooms = await roomService .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); - _logger.LogInformation("Cleaned {0} entities",rooms); + _logger.LogInformation("Cleaned {0} entities", rooms.ToString()); } public Task StopAsync(CancellationToken cancellationToken) { _timer?.Dispose(); + return Task.CompletedTask; } } diff --git a/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server.Bll/Services/Interfaces/ILongPollingService.cs new file mode 100644 index 0000000..7e71529 --- /dev/null +++ b/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Server.Bll.Services +{ + public interface ILongPollingService + { + Task CheckRoomState(int roomId); + Task CheckRoundState(int roundId); + } +} \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index 7880f28..80132f2 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -15,19 +15,16 @@ public LongPollingService(ServerContext serverContext) public async Task CheckRoomState(int roomId) { - var thisRoom = await _serverContext.Rooms.FindAsync(roomId); + var thisRoom = await _serverContext.Rooms.FindAsync(roomId.ToString()); + return thisRoom != null; } public async Task CheckRoundState(int roundId) { - var thisRound = await _serverContext.Rounds.FindAsync(roundId); + var thisRound = await _serverContext.Rounds.FindAsync(roundId.ToString()); + return thisRound is {WinnerId: null}; } } - public interface ILongPollingService - { - Task CheckRoomState(int roomId); - Task CheckRoundState(int roundId); - } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 53b9d93..8bfb36d 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -30,10 +30,12 @@ public async Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable - .FirstOrDefaultAsync(x=>x.FirstPlayerId == userId + .FirstOrDefaultAsync(x => x.FirstPlayerId == userId || x.SecondPlayerId == userId); if (doesRoomExist != null) + { return new CustomException(ExceptionTemplates.TwinkRoom); + } var room = new Room { @@ -42,14 +44,17 @@ public async Task> IsFull = false, CreationTimeTicks = DateTimeOffset.Now.Ticks }; + await _rooms.AddAsync(room); await _repository.SaveChangesAsync(); + var newRoomPlayers = new RoomPlayers { RoomId = room.Id, FirstPlayerId = userId, PlayersCount = 1 }; + if (isTraining) { var bot = await _repository.Accounts.FirstAsync(x => x.Login == "BOT"); @@ -61,6 +66,7 @@ public async Task> _rooms.Update(room); await _repository.SaveChangesAsync(); + return room.Adapt(); } @@ -82,29 +88,41 @@ public async Task> JoinRoom(int userId, bool i } if (thisRoom == null) + { return new CustomException(ExceptionTemplates.RoomNotExists); + } if (thisRoom.RoomPlayers.FirstPlayerId == userId || thisRoom.RoomPlayers.SecondPlayerId == userId) + { return new CustomException(ExceptionTemplates.AlreadyInRoom); + } + if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + { return new CustomException(ExceptionTemplates.RoomFull); - + } + if (thisRoom.RoomPlayers.FirstPlayerId != 0) { thisRoom.RoomPlayers.FirstPlayerId = userId; _rooms.Update(thisRoom); await _repository.SaveChangesAsync(); + return thisRoom.Adapt(); } - if (thisRoom.RoomPlayers.SecondPlayerId == 0) + if (thisRoom.RoomPlayers.SecondPlayerId == 0) + { return new CustomException(ExceptionTemplates.Unknown); + } thisRoom.RoomPlayers.SecondPlayerId = userId; if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + { await _roundService.CreateRoundAsync(userId, thisRoom.Id); + } _rooms.Update(thisRoom); await _repository.SaveChangesAsync(); @@ -113,7 +131,7 @@ public async Task> JoinRoom(int userId, bool i public async Task> GetRoom(int roomId) { - var room = await _rooms.FindAsync(roomId); + var room = await _rooms.FindAsync(roomId.ToString()); return room != null ? room.Adapt() @@ -123,18 +141,26 @@ public async Task> GetRoom(int roomId) public async Task UpdateRoom(RoomModel room) { var thisRoom = await _rooms.FindAsync(room.Id); + if ( thisRoom == null) + { return 400; + } + var updatedRoom = room.Adapt(); thisRoom.IsFull = updatedRoom.IsFull; thisRoom.IsPrivate = updatedRoom.IsPrivate; thisRoom.RoundId = updatedRoom.RoundId; - if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) return 400; + if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) + { + return 400; + } + _repository.Update(thisRoom); + return 200; - } public async Task DeleteRoom(int userId, int roomId) @@ -142,16 +168,26 @@ public async Task> GetRoom(int roomId) var thisRoom = await _rooms .Include(x=>x.RoomPlayers) .FirstOrDefaultAsync(x=>x.Id == roomId); + if (thisRoom == null) + { return 400; + } + if (thisRoom.RoomPlayers.FirstPlayerId != userId) + { return 400; + } + if (thisRoom.RoomPlayers.SecondPlayerId != userId) + { return 400; + } _rooms.Remove(thisRoom); await _repository.SaveChangesAsync(); + return 200; } @@ -165,6 +201,7 @@ public async Task> GetRoom(int roomId) public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) { var currentDate = DateTimeOffset.Now.Ticks; + var rooms = await _rooms .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) .ToArrayAsync(); @@ -179,7 +216,6 @@ public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan ro await _repository.SaveChangesAsync(); return rooms.Length + allRound.Length; - } } } \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index 511da27..aa604ed 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -28,6 +28,7 @@ public async Task GetPersonalStatistics(int userId) var statistics = await _repository.StatisticsEnumerable .Include(x=>x.Account) .FirstOrDefaultAsync(x=>x.Id == userId); + return statistics.Adapt(); } } diff --git a/Server.Host/Extensions/DatabaseExtension.cs b/Server.Dal/Extensions/DatabaseExtension.cs similarity index 96% rename from Server.Host/Extensions/DatabaseExtension.cs rename to Server.Dal/Extensions/DatabaseExtension.cs index 120bd33..fcbc1ee 100644 --- a/Server.Host/Extensions/DatabaseExtension.cs +++ b/Server.Dal/Extensions/DatabaseExtension.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Server.Dal.Context; -namespace Server.Extensions +namespace Server.Dal.Extensions { public static class DatabaseExtension { diff --git a/Server.Host/Extensions/SeedingExtension.cs b/Server.Dal/Extensions/SeedingExtension.cs similarity index 88% rename from Server.Host/Extensions/SeedingExtension.cs rename to Server.Dal/Extensions/SeedingExtension.cs index 9e8fc35..835706d 100644 --- a/Server.Host/Extensions/SeedingExtension.cs +++ b/Server.Dal/Extensions/SeedingExtension.cs @@ -3,7 +3,7 @@ using Server.Dal.Context; using Server.Dal.Entities; -namespace Server.Extensions +namespace Server.Dal.Extensions { public static class SeedingExtension { @@ -14,9 +14,10 @@ public static async Task EnsureBotCreated(this ServerContext context) await context.AddAsync(new Account { Id = 0, - Login = "BOT", + Login = "bot", Password = "SKJSDKBNDFB21321412UIWHFDKSJGNKSDJGN" }); + await context.SaveChangesAsync(); } } diff --git a/Server.Host/Contracts/AccountDto.cs b/Server.Host/Contracts/AccountDto.cs index 81e7c31..4d0356a 100644 --- a/Server.Host/Contracts/AccountDto.cs +++ b/Server.Host/Contracts/AccountDto.cs @@ -1,7 +1,6 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Server.Contracts +namespace Server.Host.Contracts { public record AccountDto { diff --git a/Server.Host/Contracts/Requests/LoginRequest.cs b/Server.Host/Contracts/Requests/LoginRequest.cs index 7a095be..e3742fa 100644 --- a/Server.Host/Contracts/Requests/LoginRequest.cs +++ b/Server.Host/Contracts/Requests/LoginRequest.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; -namespace Server.Contracts.Requests +namespace Server.Host.Contracts.Requests { public class LoginRequest { diff --git a/Server.Host/Contracts/Requests/RegisterRequest.cs b/Server.Host/Contracts/Requests/RegisterRequest.cs index b6ef61a..4b27984 100644 --- a/Server.Host/Contracts/Requests/RegisterRequest.cs +++ b/Server.Host/Contracts/Requests/RegisterRequest.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Server.Contracts.Requests +namespace Server.Host.Contracts.Requests { public class RegisterRequest { diff --git a/Server.Host/Contracts/StatisticsDto.cs b/Server.Host/Contracts/StatisticsDto.cs index 0770d11..a706ba3 100644 --- a/Server.Host/Contracts/StatisticsDto.cs +++ b/Server.Host/Contracts/StatisticsDto.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace Server.Contracts +namespace Server.Host.Contracts { public class StatisticsDto { diff --git a/Server.Host/Contracts/ViewModels/AccountViewModel.cs b/Server.Host/Contracts/ViewModels/AccountViewModel.cs index 8e2ec39..befa9bb 100644 --- a/Server.Host/Contracts/ViewModels/AccountViewModel.cs +++ b/Server.Host/Contracts/ViewModels/AccountViewModel.cs @@ -1,4 +1,4 @@ -namespace Server.Contracts.ViewModels +namespace Server.Host.Contracts.ViewModels { public class AccountViewModel { diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index cf84a7f..91b4534 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -4,10 +4,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Server.Authentication.Services; -using Server.Contracts; -using Server.Contracts.Requests; +using Server.Host.Contracts; +using Server.Host.Contracts.Requests; -namespace Server.Controllers +namespace Server.Host.Controllers { [ApiController] [Route ("api/v1/account/")] diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index 576a88f..b13388a 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Server.Bll.Services; -namespace Server.Controllers +namespace Server.Host.Controllers { [ApiController] [Route("api/v1")] diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 9aeb47d..f9e4769 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -1,17 +1,14 @@ -using System; -using System.Net; +using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using OneOf; using Server.Authentication.Models.Interfaces; -using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -namespace Server.Controllers +namespace Server.Host.Controllers { [ApiController] [Route("api/v1/room/")] diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 1f6c186..8307a38 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -8,7 +8,7 @@ using Server.Bll.Models; using Server.Bll.Services.Interfaces; -namespace Server.Controllers +namespace Server.Host.Controllers { /// /// API Round Controller diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index b1a3dd5..9beae10 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -7,9 +7,9 @@ using Server.Authentication.Models.Interfaces; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using Server.Contracts; +using Server.Host.Contracts; -namespace Server.Controllers +namespace Server.Host.Controllers { [ApiController] diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index d19c2dd..70827ff 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Server.Extensions +namespace Server.Host.Extensions { internal class LoggingMiddleware { diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index 86354fa..b9ed017 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -2,11 +2,10 @@ using Server.Authentication.Models; using Server.Authentication.Models.Interfaces; using Server.Bll.HostedServices; -using Server.Bll.Models; using Server.Bll.Services; using Server.Bll.Services.Interfaces; -namespace Server.Extensions +namespace Server.Host.Extensions { public static class ServiceExtension { diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index 46b4886..97c7bf8 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; -namespace Server.Extensions +namespace Server.Host.Extensions { /// /// Swagger extension diff --git a/Server.Host/Program.cs b/Server.Host/Program.cs index 6672702..4aef788 100644 --- a/Server.Host/Program.cs +++ b/Server.Host/Program.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using Serilog; -namespace Server +namespace Server.Host { public static class Program { @@ -13,7 +13,7 @@ public static void Main(string[] args) } private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureLogging(loggingBuilder => { loggingBuilder.ClearProviders(); diff --git a/Server.Host/Properties/launchSettings.json b/Server.Host/Properties/launchSettings.json index 604abbb..77b4820 100644 --- a/Server.Host/Properties/launchSettings.json +++ b/Server.Host/Properties/launchSettings.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { - "Server": { + "Server.Host": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index d98634c..17e6ae9 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -2,7 +2,6 @@ net5.0 - Server diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index 0761e74..1eb9158 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -6,9 +6,10 @@ using Microsoft.Extensions.Hosting; using Server.Authentication.Extensions; using Server.Dal.Context; -using Server.Extensions; +using Server.Dal.Extensions; +using Server.Host.Extensions; -namespace Server +namespace Server.Host { public class Startup { From 452be65710f8b519ae246b7ff09b335a3a51b15a Mon Sep 17 00:00:00 2001 From: igorvolokhovych Date: Tue, 26 Apr 2022 18:39:35 +0300 Subject: [PATCH 28/56] Sealing + scoped --- Client/Client.csproj | 2 +- RockPaperScissorsGame.sln | 13 +- Server.Authentication/AuthOptions.cs | 63 +++-- .../Exceptions/UserException.cs | 29 +-- .../Exceptions/UserExceptionsTemplates.cs | 17 +- .../Extensions/AuthenticationExtension.cs | 77 +++--- Server.Authentication/HashingBase64.cs | 37 ++- .../Models/AccountOutputModel.cs | 11 +- .../Models/ApplicationUser.cs | 46 ++-- Server.Authentication/Models/AuthUser.cs | 45 ++-- .../Models/Interfaces/IApplicationUser.cs | 17 +- .../Models/Interfaces/IAuthUser.cs | 17 +- Server.Authentication/Models/Roles.cs | 25 +- .../Server.Authentication.csproj | 2 +- .../Services/AttemptValidationService.cs | 73 +++--- Server.Authentication/Services/AuthService.cs | 244 +++++++++--------- .../Services/IAuthService.cs | 11 +- Server.Bll/Server.Bll.csproj | 2 +- Server.Dal/Context/ServerContext.cs | 79 +++--- Server.Dal/Entities/Account.cs | 59 +++-- Server.Dal/Entities/Room.cs | 82 +++--- Server.Dal/Entities/RoomPlayers.cs | 35 ++- Server.Dal/Entities/Round.cs | 48 ++-- Server.Dal/Entities/Statistics.cs | 111 ++++---- Server.Dal/Extensions/DatabaseExtension.cs | 23 +- Server.Dal/Extensions/SeedingExtension.cs | 30 ++- Server.Dal/Server.Dal.csproj | 2 +- Server.Host/Contracts/AccountDto.cs | 18 +- .../Contracts/Requests/LoginRequest.cs | 19 +- .../Contracts/Requests/RegisterRequest.cs | 17 +- Server.Host/Contracts/StatisticsDto.cs | 15 +- .../Contracts/ViewModels/AccountViewModel.cs | 11 +- Server.Host/Controllers/AccountController.cs | 89 ++++--- .../Controllers/LongPollingController.cs | 41 ++- Server.Host/Controllers/RoomController.cs | 139 +++++----- Server.Host/Controllers/RoundController.cs | 95 ++++--- .../Controllers/StatisticsController.cs | 58 ++--- Server.Host/Extensions/LoggingMiddleware.cs | 151 ++++++----- Server.Host/Extensions/ServiceExtension.cs | 39 ++- Server.Host/Extensions/SwaggerExtension.cs | 83 +++--- Server.Host/Program.cs | 35 ++- Server.Host/Server.Host.csproj | 2 +- Server.Host/Startup.cs | 80 +++--- 43 files changed, 1031 insertions(+), 1061 deletions(-) diff --git a/Client/Client.csproj b/Client/Client.csproj index 8af9724..a2c80ad 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6 diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 37b6cce..128ec5c 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -11,10 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Dal", "Server.Dal\Se EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server.Bll\Server.Bll.csproj", "{FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libs", "Libs", "{09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Authentication", "Server.Authentication\Server.Authentication.csproj", "{004A30E1-2903-4C9F-8D02-EEBE67C677BD}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{F85F3652-F3F7-4EBC-AEE5-16B7E4BB09D0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{C11A7F35-798A-4410-BFAE-CC29502DEF15}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,7 +51,10 @@ Global SolutionGuid = {4ADC353B-52B1-43B2-B26D-3279A6ACC978} EndGlobalSection GlobalSection(NestedProjects) = preSolution - {004A30E1-2903-4C9F-8D02-EEBE67C677BD} = {09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8} - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430} = {09D773DA-B3F6-4A1F-BEC3-7A8B2BC624E8} + {B6D69162-F9E0-4165-A288-00FF1D073291} = {F85F3652-F3F7-4EBC-AEE5-16B7E4BB09D0} + {004A30E1-2903-4C9F-8D02-EEBE67C677BD} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} + {1207E8F7-D6FF-4F7D-B5A1-55D96F286430} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} + {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} + {53166F83-4032-4CCF-B172-C2517BFBA424} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} EndGlobalSection EndGlobal diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index 7202e4c..a2e4a1c 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -5,45 +5,44 @@ using Microsoft.IdentityModel.Tokens; using Server.Authentication.Models; -namespace Server.Authentication +namespace Server.Authentication; + +/// +/// Options for JWT token +/// +public sealed class AuthOptions { /// - /// Options for JWT token + /// Token issuer (producer). /// - public class AuthOptions - { - /// - /// Token issuer (producer). - /// - public string Issuer { get; set; } = "RPC"; + public string Issuer { get; set; } = "RPC"; - /// - /// Token audience (consumer). - /// - public string Audience { get; set; } = "Server.Host"; + /// + /// Token audience (consumer). + /// + public string Audience { get; set; } = "Server.Host"; - /// - /// Token secret part. - /// - public string PrivateKey { get; set; } = "RockPaperScissors"; + /// + /// Token secret part. + /// + public string PrivateKey { get; set; } = "RockPaperScissors"; - /// - /// Token life time. - /// - public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3d); + /// + /// Token life time. + /// + public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3d); - /// - /// Require HTTPS. - /// - public bool RequireHttps { get; set; } = false; + /// + /// Require HTTPS. + /// + public bool RequireHttps { get; set; } = false; - /// - /// Getting a symmetric security key - /// - /// - public SymmetricSecurityKey GetSymmetricSecurityKey() - { - return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey)); - } + /// + /// Getting a symmetric security key + /// + /// + public SymmetricSecurityKey GetSymmetricSecurityKey() + { + return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey)); } } \ No newline at end of file diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs index 247ae85..092222b 100644 --- a/Server.Authentication/Exceptions/UserException.cs +++ b/Server.Authentication/Exceptions/UserException.cs @@ -1,23 +1,22 @@ using System; using Microsoft.AspNetCore.Http; -namespace Server.Authentication.Exceptions +namespace Server.Authentication.Exceptions; + +public readonly struct UserException { - public readonly struct UserException - { - public int Code { get; } - public string Message { get; } + public int Code { get; } + public string Message { get; } - public UserException(string messageType, string message, DateTimeOffset dateTimeOffset) - { - Code = StatusCodes.Status400BadRequest; - Message = string.Format(messageType, message, dateTimeOffset.ToString("f")); - } + public UserException(string messageType, string message, DateTimeOffset dateTimeOffset) + { + Code = StatusCodes.Status400BadRequest; + Message = string.Format(messageType, message, dateTimeOffset.ToString("f")); + } - public UserException(string invalidCredentialsForUser, string loginName) - { - Code = StatusCodes.Status400BadRequest; - Message = string.Format(invalidCredentialsForUser, loginName); - } + public UserException(string invalidCredentialsForUser, string loginName) + { + Code = StatusCodes.Status400BadRequest; + Message = string.Format(invalidCredentialsForUser, loginName); } } \ No newline at end of file diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs index 7935252..e8e6cf7 100644 --- a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs +++ b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -1,11 +1,10 @@ -namespace Server.Authentication.Exceptions +namespace Server.Authentication.Exceptions; + +internal static class UserExceptionsTemplates { - public static class UserExceptionsTemplates - { - public const string UserCoolDown = "User [{0}] has been cooled down till [{1}]."; - public const string UserInvalidCredentials = "Invalid Crenedtials for user [{0}]."; - public const string UserNotFound = "User with login [{0}] is not found."; - public const string UserAlreadyExists = "User with login [{0}] already exists."; - public const string UnknownError = "Unknown error occured. Please try again."; - } + internal const string UserCoolDown = "User [{0}] has been cooled down till [{1}]."; + internal const string UserInvalidCredentials = "Invalid Crenedtials for user [{0}]."; + internal const string UserNotFound = "User with login [{0}] is not found."; + internal const string UserAlreadyExists = "User with login [{0}] already exists."; + internal const string UnknownError = "Unknown error occured. Please try again."; } \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index 12f3720..be46318 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -1,57 +1,58 @@ using System; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Server.Authentication.Models; +using Server.Authentication.Models.Interfaces; using Server.Authentication.Services; -namespace Server.Authentication.Extensions +namespace Server.Authentication.Extensions; + +/// +/// Authentication services extension +/// +public static class AuthenticationExtension { /// - /// Authentication services extension + /// Register authentication /// - public static class AuthenticationExtension + /// Service collection + /// + public static IServiceCollection AddAuthentications(this IServiceCollection services) { - /// - /// Register authentication - /// - /// Service collection - /// - public static IServiceCollection AddAuthentications(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); + if (services == null) throw new ArgumentNullException(nameof(services)); - services.AddOptions(); - var jwtOptions = services - .BuildServiceProvider() - .GetRequiredService>() - .Value; + services.AddOptions(); + + var jwtOptions = services + .BuildServiceProvider() + .GetRequiredService>() + .Value; - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = jwtOptions.RequireHttps; + options.TokenValidationParameters = new TokenValidationParameters { - options.RequireHttpsMetadata = jwtOptions.RequireHttps; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = jwtOptions.Issuer, + ValidateIssuer = true, + ValidIssuer = jwtOptions.Issuer, - ValidateAudience = true, - ValidAudience = jwtOptions.Audience, - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero, + ValidateAudience = true, + ValidAudience = jwtOptions.Audience, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, - IssuerSigningKey = jwtOptions.GetSymmetricSecurityKey(), - ValidateIssuerSigningKey = true - }; - }); + IssuerSigningKey = jwtOptions.GetSymmetricSecurityKey(), + ValidateIssuerSigningKey = true + }; + }); - services - .AddTransient() - .AddSingleton(typeof(AttemptValidationService)); - return services; - } + services + .AddTransient() + .AddTransient() + .AddSingleton(typeof(AttemptValidationService)); + return services; } } \ No newline at end of file diff --git a/Server.Authentication/HashingBase64.cs b/Server.Authentication/HashingBase64.cs index 92b9048..dae0792 100644 --- a/Server.Authentication/HashingBase64.cs +++ b/Server.Authentication/HashingBase64.cs @@ -1,29 +1,28 @@ using System; -namespace Server.Authentication +namespace Server.Authentication; + +public static class HashingBase64 { - public static class HashingBase64 + public static string DecodeBase64(this string encodedData) { - public static string DecodeBase64(this string encodedData) - { - var encodedDataAsBytes - = Convert.FromBase64String(encodedData); - return - System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); - } + var encodedDataAsBytes + = Convert.FromBase64String(encodedData); + return + System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); + } - public static string EncodeBase64(this string decodedData) - { - var dataArray = System.Text.Encoding.ASCII.GetBytes(decodedData); + public static string EncodeBase64(this string decodedData) + { + var dataArray = System.Text.Encoding.ASCII.GetBytes(decodedData); - return Convert.ToBase64String(dataArray); - } + return Convert.ToBase64String(dataArray); + } - public static bool IsHashEqual(this string base64Data, string initialData) - { - var baseDecoded = EncodeBase64(initialData); + public static bool IsHashEqual(this string base64Data, string initialData) + { + var baseDecoded = EncodeBase64(initialData); - return base64Data.Equals(baseDecoded, StringComparison.OrdinalIgnoreCase); - } + return base64Data.Equals(baseDecoded, StringComparison.OrdinalIgnoreCase); } } \ No newline at end of file diff --git a/Server.Authentication/Models/AccountOutputModel.cs b/Server.Authentication/Models/AccountOutputModel.cs index fabbd5a..62c3251 100644 --- a/Server.Authentication/Models/AccountOutputModel.cs +++ b/Server.Authentication/Models/AccountOutputModel.cs @@ -1,8 +1,7 @@ -namespace Server.Authentication.Models +namespace Server.Authentication.Models; + +public sealed class AccountOutputModel { - public class AccountOutputModel - { - public string Token { get; set; } - public string Login { get; set; } - } + public string Token { get; set; } + public string Login { get; set; } } \ No newline at end of file diff --git a/Server.Authentication/Models/ApplicationUser.cs b/Server.Authentication/Models/ApplicationUser.cs index 3150967..5ba6591 100644 --- a/Server.Authentication/Models/ApplicationUser.cs +++ b/Server.Authentication/Models/ApplicationUser.cs @@ -1,36 +1,36 @@ +using System; using System.Linq; using Microsoft.AspNetCore.Http; using Server.Authentication.Models.Interfaces; -namespace Server.Authentication.Models +namespace Server.Authentication.Models; + +/// +/// Application user +/// +internal sealed class ApplicationUser: IApplicationUser { + private readonly IHttpContextAccessor _httpContextAccessor; + /// - /// Application user + /// Constructor /// - public class ApplicationUser: IApplicationUser + /// . + public ApplicationUser(IHttpContextAccessor httpContextAccessor) { - private readonly IHttpContextAccessor _httpContextAccessor; - - /// - /// Constructor - /// - /// http accessor - public ApplicationUser(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + } - /// - /// This user id - /// - public int Id => GetUserId(); + /// + /// This user id + /// + public int Id => GetUserId(); - private int GetUserId() - { - var request = _httpContextAccessor.HttpContext - ?.User.Claims.FirstOrDefault(x => x.Type == "id"); + private int GetUserId() + { + var request = _httpContextAccessor.HttpContext + ?.User.Claims.FirstOrDefault(x => x.Type == "id"); - return int.TryParse(request?.Value, out var id) ? id : 0; - } + return int.TryParse(request?.Value, out var id) ? id : 0; } } \ No newline at end of file diff --git a/Server.Authentication/Models/AuthUser.cs b/Server.Authentication/Models/AuthUser.cs index 7c15b50..45dbcaa 100644 --- a/Server.Authentication/Models/AuthUser.cs +++ b/Server.Authentication/Models/AuthUser.cs @@ -2,35 +2,34 @@ using Microsoft.AspNetCore.Http; using Server.Authentication.Models.Interfaces; -namespace Server.Authentication.Models +namespace Server.Authentication.Models; + +/// +/// Application user +/// +internal sealed class AuthUser: IAuthUser { + private readonly IHttpContextAccessor _httpContextAccessor; + /// - /// Application user + /// Constructor /// - public class AuthUser: IAuthUser + /// http accessor + public AuthUser(IHttpContextAccessor httpContextAccessor) { - private readonly IHttpContextAccessor _httpContextAccessor; - - /// - /// Constructor - /// - /// http accessor - public AuthUser(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } + _httpContextAccessor = httpContextAccessor; + } - /// - /// This user шв - /// - public int Id => GetUserId(); + /// + /// This user шв + /// + public int Id => GetUserId(); - private int GetUserId() - { - var request = _httpContextAccessor.HttpContext - ?.User.Claims.FirstOrDefault(x => x.Type == "id"); + private int GetUserId() + { + var request = _httpContextAccessor.HttpContext + ?.User.Claims.FirstOrDefault(x => x.Type == "id"); - return int.TryParse(request?.Value, out var id) ? id : 0; - } + return int.TryParse(request?.Value, out var id) ? id : 0; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs index 66248be..83e00b9 100644 --- a/Server.Authentication/Models/Interfaces/IApplicationUser.cs +++ b/Server.Authentication/Models/Interfaces/IApplicationUser.cs @@ -1,13 +1,12 @@ -namespace Server.Authentication.Models.Interfaces +namespace Server.Authentication.Models.Interfaces; + +/// +/// Interface for ApplicationUser class +/// +public interface IApplicationUser { /// - /// Interface for ApplicationUser class + /// User request id from token /// - public interface IApplicationUser - { - /// - /// User request id from token - /// - int Id { get; } - } + int Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IAuthUser.cs b/Server.Authentication/Models/Interfaces/IAuthUser.cs index 3e65afb..ca21109 100644 --- a/Server.Authentication/Models/Interfaces/IAuthUser.cs +++ b/Server.Authentication/Models/Interfaces/IAuthUser.cs @@ -1,13 +1,12 @@ -namespace Server.Authentication.Models.Interfaces +namespace Server.Authentication.Models.Interfaces; + +/// +/// Interface for ApplicationUser class +/// +public interface IAuthUser { /// - /// Interface for ApplicationUser class + /// User request id from token /// - public interface IAuthUser - { - /// - /// User request id from token - /// - int Id { get; } - } + int Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Roles.cs b/Server.Authentication/Models/Roles.cs index c113034..0a3b6b6 100644 --- a/Server.Authentication/Models/Roles.cs +++ b/Server.Authentication/Models/Roles.cs @@ -1,17 +1,16 @@ -namespace Server.Authentication.Models +namespace Server.Authentication.Models; + +/// +/// User roles +/// +public static class Roles { /// - /// User roles + /// Admin role /// - public static class Roles - { - /// - /// Admin role - /// - public const string Admin = "admin"; - /// - /// User role - /// - public const string User = "user"; - } + public const string Admin = "admin"; + /// + /// User role + /// + public const string User = "user"; } \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 0399183..04292ac 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -1,7 +1,7 @@ - net5.0 + net6 diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 44eda81..3738b17 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -1,53 +1,52 @@ using System; using System.Collections.Concurrent; -namespace Server.Authentication.Services +namespace Server.Authentication.Services; + +public sealed class AttemptValidationService { - public class AttemptValidationService - { - // key - userId. Value - failed attempts - private readonly ConcurrentDictionary _failedAttempts = new(); - private readonly ConcurrentDictionary _coolDownCollection = new(); + // key - userId. Value - failed attempts + private readonly ConcurrentDictionary _failedAttempts = new(); + private readonly ConcurrentDictionary _coolDownCollection = new(); - public bool InsertFailAttempt(string userId) + public bool InsertFailAttempt(string userId) + { + if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) { - if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) + if (failedAttempts >= 2) { - if (failedAttempts >= 2) - { - // todo: in options - _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); - _failedAttempts.TryRemove(userId, out _); - return true; - } + // todo: in options + _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); + _failedAttempts.TryRemove(userId, out _); + return true; } - _failedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); - - return true; - } - - public int? CountFailedAttempts(string userId) - { - return _failedAttempts.TryGetValue(userId, out var failedAttempts) - ? failedAttempts - : default; } + _failedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); + + return true; + } - public bool IsCoolDown(string userId, out DateTimeOffset coolDownDate) - { - var result = _coolDownCollection.TryGetValue(userId, out coolDownDate); + public int? CountFailedAttempts(string userId) + { + return _failedAttempts.TryGetValue(userId, out var failedAttempts) + ? failedAttempts + : default; + } - if (!result) - { - return false; - } - if (coolDownDate >= DateTimeOffset.Now) - { - return true; - } - _coolDownCollection.TryRemove(userId, out coolDownDate); + public bool IsCoolDown(string userId, out DateTimeOffset coolDownDate) + { + var result = _coolDownCollection.TryGetValue(userId, out coolDownDate); + if (!result) + { return false; } + if (coolDownDate >= DateTimeOffset.Now) + { + return true; + } + _coolDownCollection.TryRemove(userId, out coolDownDate); + + return false; } } \ No newline at end of file diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 9499eb9..eb4bb37 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -13,155 +13,157 @@ using Server.Dal.Context; using Server.Dal.Entities; -namespace Server.Authentication.Services +namespace Server.Authentication.Services; + +/// +/// Implements . +/// +internal sealed class AuthService : IAuthService { - /// - /// Implements . - /// - internal class AuthService : IAuthService - { - private readonly ServerContext _repository; - private readonly AttemptValidationService _attemptValidationService; - private readonly AuthOptions _authOptions; - private readonly ILogger _logger; + private readonly ServerContext _repository; + private readonly AttemptValidationService _attemptValidationService; + private readonly AuthOptions _authOptions; + private readonly ILogger _logger; - private readonly SemaphoreSlim _semaphore = new(1, 1); - private readonly DbSet _accounts; + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly DbSet _accounts; - public AuthService( - ILogger logger, - IOptions authOptions, - AttemptValidationService attemptValidationService, - ServerContext repository) + public AuthService( + ILogger logger, + IOptions authOptions, + AttemptValidationService attemptValidationService, + ServerContext repository) + { + _logger = logger; + _attemptValidationService = attemptValidationService; + _repository = repository; + _authOptions = authOptions.Value; + _accounts = repository.Accounts; + } + + /// + public async Task> + RegisterAsync(string login, string password) + { + if (string.IsNullOrWhiteSpace(login)) { - _logger = logger; - _attemptValidationService = attemptValidationService; - _repository = repository; - _authOptions = authOptions.Value; - _accounts = repository.Accounts; + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(login)); } - - /// - public async Task> - RegisterAsync(string login, string password) + + if (string.IsNullOrEmpty(password)) { - if (string.IsNullOrWhiteSpace(login)) - { - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(login)); - } + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(password)); + } - if (string.IsNullOrEmpty(password)) + var release = await _semaphore.WaitAsync(1000); + + try + { + if (await _accounts.CountAsync(x=>x.Login == login) > 0) { - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(password)); + return new UserException(UserExceptionsTemplates.UserAlreadyExists, login); } - - var release = await _semaphore.WaitAsync(1000); - try - { - if (await _accounts.CountAsync(x=>x.Login == login) > 0) - { - return new UserException(UserExceptionsTemplates.UserAlreadyExists, login); - } - var account = new Account - { - Login = login, - Password = password.EncodeBase64() - }; - - await _accounts.AddAsync(account); - await _repository.SaveChangesAsync(); + var account = new Account + { + Login = login, + Password = password.EncodeBase64() + }; - var accountStatistics = new Statistics - { - AccountId = account.Id - }; - await _repository.StatisticsEnumerable.AddAsync(accountStatistics); + await _accounts.AddAsync(account); + await _repository.SaveChangesAsync(); - await _repository.SaveChangesAsync(); - return 200; - } - catch + var accountStatistics = new Statistics { - _logger.LogWarning("Unable to process account for {Login}", login); - return new UserException(UserExceptionsTemplates.UnknownError,string.Empty); - } - finally - { - if (release) - _semaphore.Release(); - } + AccountId = account.Id + }; + await _repository.StatisticsEnumerable.AddAsync(accountStatistics); + + await _repository.SaveChangesAsync(); + + return 200; } - - /// - public async Task> LoginAsync(string login, string password) + catch { - var userAccount = await _accounts.FirstOrDefaultAsync(x => x.Login == login); + _logger.LogWarning("Unable to process account for {Login}", login); + + return new UserException(UserExceptionsTemplates.UnknownError,string.Empty); + } + finally + { + if (release) + _semaphore.Release(); + } + } + + /// + public async Task> LoginAsync(string login, string password) + { + var userAccount = await _accounts.FirstOrDefaultAsync(x => x.Login == login); - if (userAccount is null) - { - return new UserException(UserExceptionsTemplates.UserNotFound,login); - } + if (userAccount is null) + { + return new UserException(UserExceptionsTemplates.UserNotFound,login); + } - if (_attemptValidationService.IsCoolDown(login, out var coolRequestDate)) - { - return new UserException(UserExceptionsTemplates.UserCoolDown, login, coolRequestDate); - } + if (_attemptValidationService.IsCoolDown(login, out var coolRequestDate)) + { + return new UserException(UserExceptionsTemplates.UserCoolDown, login, coolRequestDate); + } - if (userAccount.Password.IsHashEqual(login)) + if (userAccount.Password.IsHashEqual(login)) + { + return new AccountOutputModel { - return new AccountOutputModel - { - Token = BuildToken(userAccount), - Login = userAccount.Login - }; - } + Token = BuildToken(userAccount), + Login = userAccount.Login + }; + } - _attemptValidationService.InsertFailAttempt(login); + _attemptValidationService.InsertFailAttempt(login); - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, login); - } + return new UserException(UserExceptionsTemplates.UserInvalidCredentials, login); + } - public Task RemoveAsync(int accountId) - { - throw new NotImplementedException(); - } + public Task RemoveAsync(int accountId) + { + throw new NotImplementedException(); + } - private string BuildToken(Account accountModel) - { - var identity = GetClaimsIdentity(accountModel.Id); + private string BuildToken(Account accountModel) + { + var identity = GetClaimsIdentity(accountModel.Id); - var now = DateTime.UtcNow; - var jwtToken = new JwtSecurityToken( - issuer: _authOptions.Issuer, - audience: _authOptions.Audience, - notBefore: now, - claims: identity.Claims, - expires: now.Add(_authOptions.LifeTime), - signingCredentials: new SigningCredentials( - _authOptions.GetSymmetricSecurityKey(), - SecurityAlgorithms.HmacSha256)); + var now = DateTime.UtcNow; + var jwtToken = new JwtSecurityToken( + issuer: _authOptions.Issuer, + audience: _authOptions.Audience, + notBefore: now, + claims: identity.Claims, + expires: now.Add(_authOptions.LifeTime), + signingCredentials: new SigningCredentials( + _authOptions.GetSymmetricSecurityKey(), + SecurityAlgorithms.HmacSha256)); - var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwtToken); + var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwtToken); - return encodedJwt; - } + return encodedJwt; + } - private static ClaimsIdentity GetClaimsIdentity(int userId) + private static ClaimsIdentity GetClaimsIdentity(int userId) + { + var claims = new[] { - var claims = new[] - { - new Claim("id", userId.ToString()), - new Claim(ClaimsIdentity.DefaultRoleClaimType, "User") - }; - var claimsIdentity = - new ClaimsIdentity( - claims, - "Token", - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); + new Claim("id", userId.ToString()), + new Claim(ClaimsIdentity.DefaultRoleClaimType, "User") + }; + var claimsIdentity = + new ClaimsIdentity( + claims, + "Token", + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType); - return claimsIdentity; - } + return claimsIdentity; } } \ No newline at end of file diff --git a/Server.Authentication/Services/IAuthService.cs b/Server.Authentication/Services/IAuthService.cs index 8cde3c2..4af6791 100644 --- a/Server.Authentication/Services/IAuthService.cs +++ b/Server.Authentication/Services/IAuthService.cs @@ -3,11 +3,10 @@ using Server.Authentication.Exceptions; using Server.Authentication.Models; -namespace Server.Authentication.Services +namespace Server.Authentication.Services; + +public interface IAuthService { - public interface IAuthService - { - Task> RegisterAsync(string login, string password); - Task> LoginAsync(string login, string password); - } + Task> RegisterAsync(string login, string password); + Task> LoginAsync(string login, string password); } \ No newline at end of file diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 9c77cc4..9de1dd0 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -1,7 +1,7 @@ - net5.0 + net6 diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index 68431e2..861b09e 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -1,52 +1,51 @@ using Microsoft.EntityFrameworkCore; using Server.Dal.Entities; -namespace Server.Dal.Context +namespace Server.Dal.Context; + +public sealed class ServerContext : DbContext { - public class ServerContext : DbContext - { - public DbSet Accounts { get; set; } - public DbSet Rooms { get; set; } - public DbSet RoomPlayersEnumerable { get; set; } - public DbSet Rounds { get; set; } - public DbSet StatisticsEnumerable { get; set; } + public DbSet Accounts { get; set; } + public DbSet Rooms { get; set; } + public DbSet RoomPlayersEnumerable { get; set; } + public DbSet Rounds { get; set; } + public DbSet StatisticsEnumerable { get; set; } - public ServerContext(DbContextOptions contextOptions) - :base(contextOptions) { } + public ServerContext(DbContextOptions contextOptions) + :base(contextOptions) { } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { - modelBuilder.Entity() - .HasQueryFilter(x => !x.IsFinished); + modelBuilder.Entity() + .HasQueryFilter(x => !x.IsFinished); - modelBuilder.Entity() - .HasOne(invite => invite.FirstPlayer) - .WithMany(user => user.FirstPlayer) - .HasForeignKey(invite => invite.FirstPlayerId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(players => players.Room) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasOne(invite => invite.FirstPlayer) + .WithMany(user => user.FirstPlayer) + .HasForeignKey(invite => invite.FirstPlayerId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(players => players.Room) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.RoomPlayers) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(invite => invite.SecondPlayer) - .WithMany(user => user.SecondPlayer) - .HasForeignKey(invite => invite.SecondPlayerId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(x => x.RoomPlayers) - .WithOne() - .OnDelete(DeleteBehavior.NoAction); + modelBuilder.Entity() + .HasOne(x => x.RoomPlayers) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne(invite => invite.SecondPlayer) + .WithMany(user => user.SecondPlayer) + .HasForeignKey(invite => invite.SecondPlayerId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(x => x.RoomPlayers) + .WithOne() + .OnDelete(DeleteBehavior.NoAction); - } } } \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index 1c167c9..d27d8d6 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -2,40 +2,39 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Dal.Entities; + +[Table("Accounts")] +public class Account { - [Table("Accounts")] - public class Account - { - /// - /// Id of account. Unique to everyone and similar with Statistics Id - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + /// + /// Id of account. Unique to everyone and similar with Statistics Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - /// - /// Nick name of Account - /// - public string Login { get; set; } + /// + /// Nick name of Account + /// + public string Login { get; set; } - /// - /// Password of the Account - /// - public string Password { get; set; } + /// + /// Password of the Account + /// + public string Password { get; set; } - /// - /// Statistics id, connected to this account - /// - public int? StatisticsId { get; set; } + /// + /// Statistics id, connected to this account + /// + public int? StatisticsId { get; set; } - /// - /// Linked to this player statistics - /// - [ForeignKey("StatisticsId")] - public virtual Statistics Statistics { get; set; } + /// + /// Linked to this player statistics + /// + [ForeignKey("StatisticsId")] + public virtual Statistics Statistics { get; set; } - public virtual ICollection FirstPlayer { get; set; } - public virtual ICollection SecondPlayer { get; set; } - } + public virtual ICollection FirstPlayer { get; set; } + public virtual ICollection SecondPlayer { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index 6fba0fb..c19c390 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -1,53 +1,51 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Dal.Entities; + +[Table("Rooms")] +public class Room { - [Table("Rooms")] - public class Room - { - /// - /// Id of the room. Consists of 5 randomized chars - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + /// + /// Id of the room. Consists of 5 randomized chars + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - /// - /// Special code to join a room - /// - public string RoomCode { get; set; } + /// + /// Special code to join a room + /// + public string RoomCode { get; set; } - /// - /// Id of current round - /// - public int? RoundId { get; set; } + /// + /// Id of current round + /// + public int? RoundId { get; set; } - /// - /// Round, linked to this room - /// - [ForeignKey("RoundId")] - public virtual Round Round { get; set; } + /// + /// Round, linked to this room + /// + [ForeignKey("RoundId")] + public virtual Round Round { get; set; } - public int? RoomPlayerId { get; set; } + public int? RoomPlayerId { get; set; } - [ForeignKey("RoomPlayerId")] - public virtual RoomPlayers RoomPlayers { get; set; } + [ForeignKey("RoomPlayerId")] + public virtual RoomPlayers RoomPlayers { get; set; } - /// - /// Flag is this room is private - /// - public bool IsPrivate { get; set; } + /// + /// Flag is this room is private + /// + public bool IsPrivate { get; set; } - /// - /// Flag if room is full - /// - public bool IsFull { get; set; } + /// + /// Flag if room is full + /// + public bool IsFull { get; set; } - /// - /// Creation date. After 5 minutes of inactivity will be deleted - /// - public long CreationTimeTicks { get; set; } - } -} + /// + /// Creation date. After 5 minutes of inactivity will be deleted + /// + public long CreationTimeTicks { get; set; } +} \ No newline at end of file diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index 498ec47..c853950 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -1,23 +1,22 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Dal.Entities; + +[Table("RoomPlayers")] +public class RoomPlayers { - [Table("RoomPlayers")] - public class RoomPlayers - { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } - public int PlayersCount { get; set; } - public int RoomId { get; set; } - [ForeignKey("RoomId")] - public virtual Room Room { get; set; } - public int? FirstPlayerId { get; set; } - [ForeignKey("FirstPlayerId")] - public virtual Account FirstPlayer { get; set; } - public int? SecondPlayerId { get; set; } - [ForeignKey("SecondPlayerId")] - public virtual Account SecondPlayer { get; set; } - } + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + public int PlayersCount { get; set; } + public int RoomId { get; set; } + [ForeignKey("RoomId")] + public virtual Room Room { get; set; } + public int? FirstPlayerId { get; set; } + [ForeignKey("FirstPlayerId")] + public virtual Account FirstPlayer { get; set; } + public int? SecondPlayerId { get; set; } + [ForeignKey("SecondPlayerId")] + public virtual Account SecondPlayer { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index dc91115..42d499b 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -1,29 +1,27 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Dal.Entities; + +[Table("Rounds")] +public class Round { - [Table("Rounds")] - public class Round - { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } - public int RoomPlayersId { get; set; } - [ForeignKey("RoomPlayersId")] - public virtual RoomPlayers RoomPlayers { get; set; } - public int FirstPlayerMove { get; set; } - public int SecondPlayerMove { get; set; } - public int? WinnerId { get; set; } - [ForeignKey("WinnerId")] - public virtual Account Winner { get; set; } - public int? LoserId { get; set; } - [ForeignKey("LoserId")] - public virtual Account Loser { get; set; } - public long LastMoveTicks { get; set; } - public long TimeFinishedTicks { get; set; } - public bool IsFinished { get; set; } + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; init; } + public int RoomPlayersId { get; set; } + [ForeignKey("RoomPlayersId")] + public virtual RoomPlayers RoomPlayers { get; set; } + public int FirstPlayerMove { get; set; } + public int SecondPlayerMove { get; set; } + public int? WinnerId { get; set; } + [ForeignKey("WinnerId")] + public virtual Account Winner { get; set; } + public int? LoserId { get; set; } + [ForeignKey("LoserId")] + public virtual Account Loser { get; set; } + public long LastMoveTicks { get; set; } + public long TimeFinishedTicks { get; set; } + public bool IsFinished { get; set; } - } -} +} \ No newline at end of file diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index 48d4ba7..2f159fa 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -1,75 +1,74 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities +namespace Server.Dal.Entities; + +[Table("Statistics")] +public class Statistics { - [Table("Statistics")] - public class Statistics - { - /// - /// Id of statistics. Equivalent to Account Id - /// + /// + /// Id of statistics. Equivalent to Account Id + /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - /// - /// Id of linked account - /// - public int AccountId { get; set; } + /// + /// Id of linked account + /// + public int AccountId { get; set; } - /// - /// Linked account - /// - [ForeignKey("AccountId")] - public virtual Account Account { get; set; } + /// + /// Linked account + /// + [ForeignKey("AccountId")] + public virtual Account Account { get; set; } - /// - /// Total amount of Wins - /// - public int Wins { get; set; } + /// + /// Total amount of Wins + /// + public int Wins { get; set; } - /// - /// Total amount of Loses - /// - public int Loss { get; set; } + /// + /// Total amount of Loses + /// + public int Loss { get; set; } - /// - /// Total amount of Draws. OBSOLETE - /// + /// + /// Total amount of Draws. OBSOLETE + /// - public int Draws { get; set; } + public int Draws { get; set; } - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - public double WinLossRatio { get; set; } + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + public double WinLossRatio { get; set; } - /// - /// Ratio for the last 7 days - /// - public string TimeSpent { get; set; } + /// + /// Ratio for the last 7 days + /// + public string TimeSpent { get; set; } - /// - /// Times used rock - /// - public int UsedRock { get; set; } + /// + /// Times used rock + /// + public int UsedRock { get; set; } - /// - /// Times used Paper - /// - public int UsedPaper { get; set; } + /// + /// Times used Paper + /// + public int UsedPaper { get; set; } - /// - /// Times used Scissors - /// - public int UsedScissors { get; set; } + /// + /// Times used Scissors + /// + public int UsedScissors { get; set; } - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - public int Score { get; set; } + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + public int Score { get; set; } - } } \ No newline at end of file diff --git a/Server.Dal/Extensions/DatabaseExtension.cs b/Server.Dal/Extensions/DatabaseExtension.cs index fcbc1ee..891d3f4 100644 --- a/Server.Dal/Extensions/DatabaseExtension.cs +++ b/Server.Dal/Extensions/DatabaseExtension.cs @@ -3,19 +3,18 @@ using Microsoft.Extensions.DependencyInjection; using Server.Dal.Context; -namespace Server.Dal.Extensions +namespace Server.Dal.Extensions; + +public static class DatabaseExtension { - public static class DatabaseExtension + private const string DatabaseConnection = "DatabaseConnection"; + private const string MigrationAssemblyName = "Server.Dal"; + public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { - private const string DatabaseConnection = "DatabaseConnection"; - private const string MigrationAssemblyName = "Server.Dal"; - public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) - { - return service.AddDbContext( - builder => builder.UseSqlite - (configuration.GetConnectionString(DatabaseConnection), - x => x.MigrationsAssembly(MigrationAssemblyName)), - ServiceLifetime.Transient); - } + return service.AddDbContext( + builder => builder.UseSqlite + (configuration.GetConnectionString(DatabaseConnection), + optionsBuilder => optionsBuilder.MigrationsAssembly(MigrationAssemblyName)), + ServiceLifetime.Transient); } } \ No newline at end of file diff --git a/Server.Dal/Extensions/SeedingExtension.cs b/Server.Dal/Extensions/SeedingExtension.cs index 835706d..a0b458d 100644 --- a/Server.Dal/Extensions/SeedingExtension.cs +++ b/Server.Dal/Extensions/SeedingExtension.cs @@ -1,24 +1,26 @@ +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Server.Dal.Context; using Server.Dal.Entities; -namespace Server.Dal.Extensions +namespace Server.Dal.Extensions; + +public static class SeedingExtension { - public static class SeedingExtension + private const string DefaultName = "bot"; + + public static async Task EnsureBotCreated(this ServerContext context) { - public static async Task EnsureBotCreated(this ServerContext context) - { - var bot = await context.Accounts.FirstOrDefaultAsync(x => x.Login.ToLower() == "bot"); - if (bot == null) - await context.AddAsync(new Account - { - Id = 0, - Login = "bot", - Password = "SKJSDKBNDFB21321412UIWHFDKSJGNKSDJGN" - }); + var bot = await context.Accounts.FirstOrDefaultAsync(account => account.Login.Equals(DefaultName, StringComparison.OrdinalIgnoreCase)); + if (bot == null) + await context.AddAsync(new Account + { + Id = 0, + Login = DefaultName, + Password = "SKJSDKBNDFB21321412UIWHFDKSJGNKSDJGN" + }); - await context.SaveChangesAsync(); - } + await context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/Server.Dal/Server.Dal.csproj b/Server.Dal/Server.Dal.csproj index cc16374..63d4456 100644 --- a/Server.Dal/Server.Dal.csproj +++ b/Server.Dal/Server.Dal.csproj @@ -1,7 +1,7 @@ - net5.0 + net6 diff --git a/Server.Host/Contracts/AccountDto.cs b/Server.Host/Contracts/AccountDto.cs index 4d0356a..d3eed54 100644 --- a/Server.Host/Contracts/AccountDto.cs +++ b/Server.Host/Contracts/AccountDto.cs @@ -1,13 +1,13 @@ using System.ComponentModel.DataAnnotations; -namespace Server.Host.Contracts +namespace Server.Host.Contracts; + +public sealed class AccountDto { - public record AccountDto - { - [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } - [Required(ErrorMessage = "Password is required!!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] - public string Password { get; set;} - } + [Required(ErrorMessage = "Login is required!")] + public string Login { get; set; } + + [Required(ErrorMessage = "Password is required!!")] + [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] + public string Password { get; set;} } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/LoginRequest.cs b/Server.Host/Contracts/Requests/LoginRequest.cs index e3742fa..83c2274 100644 --- a/Server.Host/Contracts/Requests/LoginRequest.cs +++ b/Server.Host/Contracts/Requests/LoginRequest.cs @@ -1,17 +1,16 @@ using System; using System.ComponentModel.DataAnnotations; -namespace Server.Host.Contracts.Requests +namespace Server.Host.Contracts.Requests; + +public sealed class LoginRequest { - public class LoginRequest - { - [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } + [Required(ErrorMessage = "Login is required!")] + public string Login { get; set; } - [Required(ErrorMessage = "Password is required!!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] - public string Password { get; set; } + [Required(ErrorMessage = "Password is required!!")] + [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] + public string Password { get; set; } - public DateTimeOffset LastRequestTime { get; set; } - } + public DateTimeOffset LastRequestTime { get; set; } } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/RegisterRequest.cs b/Server.Host/Contracts/Requests/RegisterRequest.cs index 4b27984..a0bf63a 100644 --- a/Server.Host/Contracts/Requests/RegisterRequest.cs +++ b/Server.Host/Contracts/Requests/RegisterRequest.cs @@ -1,14 +1,13 @@ using System.ComponentModel.DataAnnotations; -namespace Server.Host.Contracts.Requests +namespace Server.Host.Contracts.Requests; + +public sealed class RegisterRequest { - public class RegisterRequest - { - [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } + [Required(ErrorMessage = "Login is required!")] + public string Login { get; set; } - [Required(ErrorMessage = "Password is required!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length. Must be 6-20")] - public string Password { get; set; } - } + [Required(ErrorMessage = "Password is required!")] + [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length. Must be 6-20")] + public string Password { get; set; } } \ No newline at end of file diff --git a/Server.Host/Contracts/StatisticsDto.cs b/Server.Host/Contracts/StatisticsDto.cs index a706ba3..80e9177 100644 --- a/Server.Host/Contracts/StatisticsDto.cs +++ b/Server.Host/Contracts/StatisticsDto.cs @@ -1,13 +1,12 @@ using Newtonsoft.Json; -namespace Server.Host.Contracts +namespace Server.Host.Contracts; + +public sealed class StatisticsDto { - public class StatisticsDto - { - [JsonProperty("Login")] - public string Login { get; set; } + [JsonProperty("Login")] + public string Login { get; set; } - [JsonProperty("Score")] - public int Score { get; set; } - } + [JsonProperty("Score")] + public int Score { get; set; } } \ No newline at end of file diff --git a/Server.Host/Contracts/ViewModels/AccountViewModel.cs b/Server.Host/Contracts/ViewModels/AccountViewModel.cs index befa9bb..6bc8916 100644 --- a/Server.Host/Contracts/ViewModels/AccountViewModel.cs +++ b/Server.Host/Contracts/ViewModels/AccountViewModel.cs @@ -1,9 +1,8 @@ -namespace Server.Host.Contracts.ViewModels +namespace Server.Host.Contracts.ViewModels; + +public sealed class AccountViewModel { - public class AccountViewModel - { - public string Token { get; set; } + public string Token { get; set; } - public string Login { get; set; } - } + public string Login { get; set; } } \ No newline at end of file diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index 91b4534..f97d2fc 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -7,55 +7,54 @@ using Server.Host.Contracts; using Server.Host.Contracts.Requests; -namespace Server.Host.Controllers +namespace Server.Host.Controllers; + +[ApiController] +[Route ("api/v1/account/")] +[Consumes(MediaTypeNames.Application.Json)] +[Produces(MediaTypeNames.Application.Json)] +public sealed class AccountController : ControllerBase { - [ApiController] - [Route ("api/v1/account/")] - [Consumes(MediaTypeNames.Application.Json)] - [Produces(MediaTypeNames.Application.Json)] - public class AccountController : ControllerBase - { - private readonly IAuthService _authService; + private readonly IAuthService _authService; - public AccountController(IAuthService authService) - { - _authService = authService; - } - [HttpPost("register")] - [AllowAnonymous] - [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task Register(RegisterRequest registerRequest) - { - if (!ModelState.IsValid) return BadRequest(); - var newAccount = await _authService - .RegisterAsync(registerRequest.Login,registerRequest.Password); - return newAccount.Match( - integer => Ok(integer), - exception => BadRequest(exception)); - } + public AccountController(IAuthService authService) + { + _authService = authService; + } + [HttpPost("register")] + [AllowAnonymous] + [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task Register(RegisterRequest registerRequest) + { + if (!ModelState.IsValid) return BadRequest(); + var newAccount = await _authService + .RegisterAsync(registerRequest.Login,registerRequest.Password); + + return newAccount.Match( + integer => Ok(integer), + exception => BadRequest(exception)); + } - [HttpPost("login")] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task Login(AccountDto accountDto) - { - if (!ModelState.IsValid) return BadRequest(); - var newAccount = - await _authService.LoginAsync(accountDto.Login, accountDto.Password); + [HttpPost("login")] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] + public async Task Login(AccountDto accountDto) + { + if (!ModelState.IsValid) return BadRequest(); + var newAccount = + await _authService.LoginAsync(accountDto.Login, accountDto.Password); - return newAccount.Match( - Ok, - userException => BadRequest(userException) - ); - } + return newAccount.Match( + Ok, + userException => BadRequest(userException)); + } - [HttpGet("logout")] - [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] - public ActionResult Logout(string sessionId) - { - return Ok(sessionId); - } + [HttpGet("logout")] + [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] + public ActionResult Logout(string sessionId) + { + return Ok(sessionId); } } \ No newline at end of file diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index b13388a..b5b27e0 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -4,30 +4,29 @@ using Microsoft.AspNetCore.Mvc; using Server.Bll.Services; -namespace Server.Host.Controllers +namespace Server.Host.Controllers; + +[ApiController] +[Route("api/v1")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public sealed class LongPollingController : ControllerBase { - [ApiController] - [Route("api/v1")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - public class LongPollingController : ControllerBase - { - private readonly ILongPollingService _longPollingService; + private readonly ILongPollingService _longPollingService; - public LongPollingController(ILongPollingService longPollingService) - { - _longPollingService = longPollingService; - } + public LongPollingController(ILongPollingService longPollingService) + { + _longPollingService = longPollingService; + } - [HttpGet("room")] - public async Task CheckRoomState([FromQuery] int roomId) - { - return await _longPollingService.CheckRoomState(roomId); - } + [HttpGet("room")] + public async Task CheckRoomState([FromQuery] int roomId) + { + return await _longPollingService.CheckRoomState(roomId); + } - [HttpGet("round")] - public async Task CheckRoundState(int roundId) - { - return await _longPollingService.CheckRoundState(roundId); - } + [HttpGet("round")] + public async Task CheckRoundState(int roundId) + { + return await _longPollingService.CheckRoundState(roundId); } } \ No newline at end of file diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index f9e4769..810aa12 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -8,83 +8,82 @@ using Server.Bll.Models; using Server.Bll.Services.Interfaces; -namespace Server.Host.Controllers +namespace Server.Host.Controllers; + +[ApiController] +[Route("api/v1/room/")] +[Produces(MediaTypeNames.Application.Json)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public sealed class RoomController : ControllerBase { - [ApiController] - [Route("api/v1/room/")] - [Produces(MediaTypeNames.Application.Json)] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - public class RoomController : ControllerBase - { - private readonly IRoomService _roomService; - private readonly IApplicationUser _applicationUser; - private int UserId => _applicationUser.Id; + private readonly IRoomService _roomService; + private readonly IApplicationUser _applicationUser; + private int UserId => _applicationUser.Id; - public RoomController(IRoomService roomService, - IApplicationUser applicationUser) - { - _roomService = roomService; - _applicationUser = applicationUser; - } + public RoomController(IRoomService roomService, + IApplicationUser applicationUser) + { + _roomService = roomService; + _applicationUser = applicationUser; + } - [HttpPost("create")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task CreateRoom([FromQuery] bool isPrivate, - [FromHeader(Name="X-Training")] bool isTraining = false) - { - var newRoom = await _roomService - .CreateRoom(UserId, isPrivate, isTraining); + [HttpPost("create")] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task CreateRoom([FromQuery] bool isPrivate, + [FromHeader(Name="X-Training")] bool isTraining = false) + { + var newRoom = await _roomService + .CreateRoom(UserId, isPrivate, isTraining); - return newRoom.Match( - Ok, - exception => BadRequest(exception)); - } + return newRoom.Match( + Ok, + exception => BadRequest(exception)); + } - [HttpPost("join/public")] - public async Task JoinPublicRoom() - { - var result = await _roomService.JoinRoom(UserId, true,null); - return result.Match( - Ok, - exception => BadRequest(exception)); - } - [HttpPost("join/private")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task JoinPrivateRoom([FromQuery] string roomCode) - { - var result = await _roomService.JoinRoom(UserId, false, roomCode); - return result.Match( - Ok, - exception => BadRequest(exception)); - } + [HttpPost("join/public")] + public async Task JoinPublicRoom() + { + var result = await _roomService.JoinRoom(UserId, true,null); + return result.Match( + Ok, + exception => BadRequest(exception)); + } + [HttpPost("join/private")] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task JoinPrivateRoom([FromQuery] string roomCode) + { + var result = await _roomService.JoinRoom(UserId, false, roomCode); + return result.Match( + Ok, + exception => BadRequest(exception)); + } - [HttpGet("update")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateRoom([FromBody] RoomModel roomModel) - { - var updateResponse = await _roomService.UpdateRoom(roomModel); + [HttpGet("update")] + //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task UpdateRoom([FromBody] RoomModel roomModel) + { + var updateResponse = await _roomService.UpdateRoom(roomModel); - return updateResponse switch - { - 200 => Ok(), - _ => BadRequest() - }; - } - - [HttpDelete("delete")] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task DeleteRoom([FromQuery] int roomId) + return updateResponse switch { - var deleteResponse = await _roomService.DeleteRoom(UserId,roomId); + 200 => Ok(), + _ => BadRequest() + }; + } + + [HttpDelete("delete")] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task DeleteRoom([FromQuery] int roomId) + { + var deleteResponse = await _roomService.DeleteRoom(UserId,roomId); - return deleteResponse switch - { - 200 => Ok(), - _ => BadRequest() - }; - } + return deleteResponse switch + { + 200 => Ok(), + _ => BadRequest() + }; } -} +} \ No newline at end of file diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 8307a38..17b55de 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -8,56 +8,55 @@ using Server.Bll.Models; using Server.Bll.Services.Interfaces; -namespace Server.Host.Controllers +namespace Server.Host.Controllers; + +/// +/// API Round Controller +/// +[ApiController] +[Route("api/v1/round")] +[Produces(MediaTypeNames.Application.Json)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public sealed class RoundController:ControllerBase { + private readonly IApplicationUser _applicationUser; + private readonly IRoundService _roundService; + private int UserId => _applicationUser.Id; + public RoundController( + IRoundService roundService, + IApplicationUser applicationUser) + { + _roundService = roundService; + _applicationUser = applicationUser; + } + /// + /// Creates round in room + /// + /// id of the room + /// + [HttpPost("create")] + //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task CreateRound(int roomId) + { + var result = await _roundService.CreateRoundAsync(UserId, roomId); + return result.Match( + Ok, + exception => BadRequest(exception)); + } /// - /// API Round Controller + /// Updates current room (Patches). /// - [ApiController] - [Route("api/v1/round")] - [Produces(MediaTypeNames.Application.Json)] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - public class RoundController:ControllerBase + /// This round model from FE or client. + /// + [HttpPatch("update")] + //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task UpdateCurrentRound(RoundModel roundModel) { - private readonly IApplicationUser _applicationUser; - private readonly IRoundService _roundService; - private int UserId => _applicationUser.Id; - public RoundController( - IRoundService roundService, - IApplicationUser applicationUser) - { - _roundService = roundService; - _applicationUser = applicationUser; - } - /// - /// Creates round in room - /// - /// id of the room - /// - [HttpPost("create")] - //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task CreateRound(int roomId) - { - var result = await _roundService.CreateRoundAsync(UserId, roomId); - return result.Match( - Ok, - exception => BadRequest(exception)); - } - /// - /// Updates current room (Patches). - /// - /// This round model from FE or client. - /// - [HttpPatch("update")] - //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateCurrentRound(RoundModel roundModel) - { - var updateResult = await _roundService.UpdateRoundAsync(UserId, roundModel); - return updateResult.Match( - Ok, - exception => BadRequest(exception)); - } + var updateResult = await _roundService.UpdateRoundAsync(UserId, roundModel); + return updateResult.Match( + Ok, + exception => BadRequest(exception)); } -} +} \ No newline at end of file diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 9beae10..0a7930c 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -9,39 +9,37 @@ using Server.Bll.Services.Interfaces; using Server.Host.Contracts; -namespace Server.Host.Controllers +namespace Server.Host.Controllers; + +[ApiController] +[Route("api/v1/stats")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public sealed class StatisticsController : ControllerBase { - - [ApiController] - [Route("api/v1/stats")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - public class StatisticsController : ControllerBase + private readonly IStatisticsService _statisticsService; + private readonly IApplicationUser _applicationUser; + private int UserId => _applicationUser.Id; + public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) { - private readonly IStatisticsService _statisticsService; - private readonly IApplicationUser _applicationUser; - private int UserId => _applicationUser.Id; - public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) - { - _statisticsService = statisticsService; - _applicationUser = applicationUser; - } + _statisticsService = statisticsService; + _applicationUser = applicationUser; + } - [HttpGet("all")] - [AllowAnonymous] - [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task> GetOverallStatistics() - { - return await _statisticsService.GetAllStatistics(); - } + [HttpGet("all")] + [AllowAnonymous] + [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task> GetOverallStatistics() + { + return await _statisticsService.GetAllStatistics(); + } - [HttpGet("personal")] - [Authorize] - //[ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task GetPersonalStatistics() - { - return Ok(await _statisticsService.GetPersonalStatistics(UserId)); - } + [HttpGet("personal")] + [Authorize] + //[ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task GetPersonalStatistics() + { + return Ok(await _statisticsService.GetPersonalStatistics(UserId)); } } \ No newline at end of file diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index 70827ff..b654fae 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -6,100 +6,99 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Server.Host.Extensions +namespace Server.Host.Extensions; + +internal sealed class LoggingMiddleware { - internal class LoggingMiddleware - { - private readonly RequestDelegate _next; - private readonly ILogger _logger; + private readonly RequestDelegate _next; + private readonly ILogger _logger; - public LoggingMiddleware( - RequestDelegate next, - ILoggerFactory loggerFactory) - { - _next = next; - _logger = loggerFactory.CreateLogger(); - } + public LoggingMiddleware( + RequestDelegate next, + ILoggerFactory loggerFactory) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + } - public async Task Invoke(HttpContext context) - { - var requestInformation = - "Request information:\n" + - $"Schema:{context.Request.Scheme}\n" + - $"Content-Type:{context.Request.ContentType}" + - $"Host:{context.Request.Host}\n" + - $"Path:{context.Request.Path}\n" + - $"QueryString:{context.Request.QueryString}\n" + - $"Request Body:{await ObtainRequestBody(context.Request)}\n"; - _logger.LogWarning(requestInformation); + public async Task Invoke(HttpContext context) + { + var requestInformation = + "Request information:\n" + + $"Schema:{context.Request.Scheme}\n" + + $"Content-Type:{context.Request.ContentType}" + + $"Host:{context.Request.Host}\n" + + $"Path:{context.Request.Path}\n" + + $"QueryString:{context.Request.QueryString}\n" + + $"Request Body:{await ObtainRequestBody(context.Request)}\n"; + _logger.LogWarning(requestInformation); - var originalResponseBody = context.Response.Body; - await using var responseBody = new MemoryStream(); + var originalResponseBody = context.Response.Body; + await using var responseBody = new MemoryStream(); - context.Response.Body = responseBody; - await _next(context); + context.Response.Body = responseBody; + await _next(context); - var status = GetStatusCode(context); - var level = GetLogLevel(status); + var status = GetStatusCode(context); + var level = GetLogLevel(status); - _logger.Log(level, "Response body: LogLevel: {0}; Code: {1}\n Body: {2}", - GetLogLevel(status),status,await ObtainResponseBody(context)); + _logger.Log(level, "Response body: LogLevel: {0}; Code: {1}\n Body: {2}", + GetLogLevel(status),status,await ObtainResponseBody(context)); - await responseBody.CopyToAsync(originalResponseBody); + await responseBody.CopyToAsync(originalResponseBody); - } + } - private static async Task ObtainRequestBody(HttpRequest request) - { - request.EnableBuffering(); - var encoding = GetEncodingFromContentType(request.ContentType); - string bodyStr; + private static async Task ObtainRequestBody(HttpRequest request) + { + request.EnableBuffering(); + var encoding = GetEncodingFromContentType(request.ContentType); + string bodyStr; - using (var reader = new StreamReader(request.Body, encoding, true, 1024, true)) - { - bodyStr = await reader.ReadToEndAsync().ConfigureAwait(false); - } - request.Body.Seek(0, SeekOrigin.Begin); - return bodyStr; - } - private static async Task ObtainResponseBody(HttpContext context) + using (var reader = new StreamReader(request.Body, encoding, true, 1024, true)) { - var response = context.Response; - response.Body.Seek(0, SeekOrigin.Begin); - var encoding = GetEncodingFromContentType(response.ContentType); - using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: - false, bufferSize: 4096, leaveOpen: true); - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - response.Body.Seek(0, SeekOrigin.Begin); - return text; + bodyStr = await reader.ReadToEndAsync().ConfigureAwait(false); } - private static Encoding GetEncodingFromContentType(string contentTypeStr) + request.Body.Seek(0, SeekOrigin.Begin); + return bodyStr; + } + private static async Task ObtainResponseBody(HttpContext context) + { + var response = context.Response; + response.Body.Seek(0, SeekOrigin.Begin); + var encoding = GetEncodingFromContentType(response.ContentType); + using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: + false, bufferSize: 4096, leaveOpen: true); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + response.Body.Seek(0, SeekOrigin.Begin); + return text; + } + private static Encoding GetEncodingFromContentType(string contentTypeStr) + { + if (string.IsNullOrEmpty(contentTypeStr)) { - if (string.IsNullOrEmpty(contentTypeStr)) - { - return Encoding.UTF8; - } - ContentType contentType; - try - { - contentType = new ContentType(contentTypeStr); - } - catch (FormatException) - { - return Encoding.UTF8; - } - return string.IsNullOrEmpty(contentType.CharSet) - ? Encoding.UTF8 - : Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + return Encoding.UTF8; } - private static LogLevel GetLogLevel(int? statusCode) + ContentType contentType; + try { - return statusCode > 399 ? LogLevel.Error : LogLevel.Information; + contentType = new ContentType(contentTypeStr); } - private static int? GetStatusCode(HttpContext context) + catch (FormatException) { - return context.Response.StatusCode; + return Encoding.UTF8; } - + return string.IsNullOrEmpty(contentType.CharSet) + ? Encoding.UTF8 + : Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); } + private static LogLevel GetLogLevel(int? statusCode) + { + return statusCode > 399 ? LogLevel.Error : LogLevel.Information; + } + private static int? GetStatusCode(HttpContext context) + { + return context.Response.StatusCode; + } + } \ No newline at end of file diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index b9ed017..f71979c 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -1,34 +1,29 @@ using Microsoft.Extensions.DependencyInjection; -using Server.Authentication.Models; -using Server.Authentication.Models.Interfaces; using Server.Bll.HostedServices; using Server.Bll.Services; using Server.Bll.Services.Interfaces; -namespace Server.Host.Extensions +namespace Server.Host.Extensions; + +public static class ServiceExtension { - public static class ServiceExtension + public static IServiceCollection AddServices(this IServiceCollection service) { - public static IServiceCollection AddServices(this IServiceCollection service) - { - service - .AddTransient() - .AddTransient() - .AddTransient() - .AddHostedService(); - service.AddHttpContextAccessor(); - - // In this way I am registering multiple interfaces to one Transient instance of RoomService; - service - .AddTransient() - .AddTransient(provider => provider.GetRequiredService()) - .AddTransient(provider => provider.GetRequiredService()); + service + .AddTransient() + .AddTransient() + .AddHostedService(); + service.AddHttpContextAccessor(); - service - .AddTransient(); + // In this way I am registering multiple interfaces to one Transient instance of RoomService; + service + .AddTransient() + .AddTransient(provider => provider.GetRequiredService()) + .AddTransient(provider => provider.GetRequiredService()); - return service; + service + .AddTransient(); - } + return service; } } \ No newline at end of file diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index 97c7bf8..81e669f 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -2,60 +2,59 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; -namespace Server.Host.Extensions +namespace Server.Host.Extensions; + +/// +/// Swagger extension +/// +public static class SwaggerExtension { /// - /// Swagger extension + /// Registers swagger. /// - public static class SwaggerExtension + /// Service collection. + /// Service collection. + public static IServiceCollection AddSwagger(this IServiceCollection services) { - /// - /// Registers swagger. - /// - /// Service collection. - /// Service collection. - public static IServiceCollection AddSwagger(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); + if (services == null) throw new ArgumentNullException(nameof(services)); - //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - services.AddSwaggerGen(options => - { - //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - //options.IncludeXmlComments(xmlPath); - options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); + services.AddSwaggerGen(options => + { + //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + //options.IncludeXmlComments(xmlPath); + options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); - options.AddSecurityRequirement( - new OpenApiSecurityRequirement + options.AddSecurityRequirement( + new OpenApiSecurityRequirement + { { + new OpenApiSecurityScheme { - new OpenApiSecurityScheme + Reference = new OpenApiReference { - Reference = new OpenApiReference - { - Id = "Bearer", - Type = ReferenceType.SecurityScheme - }, + Id = "Bearer", + Type = ReferenceType.SecurityScheme }, - Array.Empty() - } - }); + }, + Array.Empty() + } + }); - options.AddSecurityDefinition( - "Bearer", - new OpenApiSecurityScheme - { - Type = SecuritySchemeType.ApiKey, - In = ParameterLocation.Header, - Scheme = "Bearer", - Name = "Authorization", - Description = "JWT token", - BearerFormat = "JWT" - }); - }); + options.AddSecurityDefinition( + "Bearer", + new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Scheme = "Bearer", + Name = "Authorization", + Description = "JWT token", + BearerFormat = "JWT" + }); + }); - return services; - } + return services; } } \ No newline at end of file diff --git a/Server.Host/Program.cs b/Server.Host/Program.cs index 4aef788..e74c45c 100644 --- a/Server.Host/Program.cs +++ b/Server.Host/Program.cs @@ -3,25 +3,24 @@ using Microsoft.Extensions.Logging; using Serilog; -namespace Server.Host +namespace Server.Host; + +public static class Program { - public static class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureLogging(loggingBuilder => - { - loggingBuilder.ClearProviders(); - loggingBuilder.SetMinimumLevel(LogLevel.Information); - loggingBuilder.AddSerilog(new LoggerConfiguration() - .WriteTo.Console() - .CreateLogger()); - }) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + CreateHostBuilder(args).Build().Run(); } + + private static IHostBuilder CreateHostBuilder(string[] args) => + Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureLogging(loggingBuilder => + { + loggingBuilder.ClearProviders(); + loggingBuilder.SetMinimumLevel(LogLevel.Information); + loggingBuilder.AddSerilog(new LoggerConfiguration() + .WriteTo.Console() + .CreateLogger()); + }) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } \ No newline at end of file diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 17e6ae9..8b0397a 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -1,7 +1,7 @@ - net5.0 + net6 diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index 1eb9158..bd209b5 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -9,58 +9,56 @@ using Server.Dal.Extensions; using Server.Host.Extensions; -namespace Server.Host +namespace Server.Host; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - private IConfiguration Configuration { get; } + private IConfiguration Configuration { get; } - public void ConfigureServices(IServiceCollection services) - { - services //.AddServices() - .AddDatabase(Configuration) - .AddSwagger() - .AddAuthentications(); - - // Adding services - services.AddServices(); + public void ConfigureServices(IServiceCollection services) + { + services + .AddDatabase(Configuration) + .AddSwagger() + .AddAuthentications(); + + services.AddServices(); - services.AddControllers(); + services.AddControllers(); - services.AddCors(); - } + services.AddCors(); + } - public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, - ServerContext serverContext) + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, + ServerContext serverContext) + { + serverContext?.Database.Migrate(); + serverContext?.EnsureBotCreated(); + if (env.IsDevelopment()) { - serverContext?.Database.Migrate(); - serverContext?.EnsureBotCreated(); - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server.Host v1")); - } + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server.Host v1")); + } - app.UseCors(builder => builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader() - .WithExposedHeaders("Authorization", "Accept", "Content-Type", "Origin")); + app.UseCors(builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .WithExposedHeaders("Authorization", "Accept", "Content-Type", "Origin")); - app.UseMiddleware(); - app.UseRouting(); + app.UseMiddleware(); + app.UseRouting(); - app.UseAuthorization(); + app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); } } \ No newline at end of file From c32806b8beb03e655c45b06f0813a9044cba8b64 Mon Sep 17 00:00:00 2001 From: igorvolokhovych Date: Tue, 26 Apr 2022 18:44:21 +0300 Subject: [PATCH 29/56] More sealing --- Server.Bll/Exceptions/CustomException.cs | 31 +- Server.Bll/Exceptions/ExceptionTemplates.cs | 33 +- .../Extensions/ServiceCollectionExtensions.cs | 29 ++ .../HostedServices/CleanerHostedService.cs | 76 ++-- Server.Bll/Models/AccountModel.cs | 9 +- Server.Bll/Models/RoomModel.cs | 27 +- Server.Bll/Models/RoomPlayersModel.cs | 20 +- Server.Bll/Models/RoundModel.cs | 27 +- Server.Bll/Models/StatisticsModel.cs | 101 +++--- .../Services/Interfaces/IHostedRoomService.cs | 10 +- .../Interfaces/ILongPollingService.cs | 11 +- .../Services/Interfaces/IRoomService.cs | 22 +- .../Services/Interfaces/IRoundService.cs | 13 +- .../Services/Interfaces/IStatisticsService.cs | 11 +- Server.Bll/Services/LongPollingService.cs | 35 +- Server.Bll/Services/RoomService.cs | 329 +++++++++--------- Server.Bll/Services/RoundService.cs | 141 ++++---- Server.Bll/Services/StatisticsService.cs | 39 +-- Server.Dal/Context/ServerContext.cs | 1 - .../Controllers/LongPollingController.cs | 1 + Server.Host/Extensions/LoggingMiddleware.cs | 1 + Server.Host/Extensions/ServiceExtension.cs | 21 +- 22 files changed, 499 insertions(+), 489 deletions(-) create mode 100644 Server.Bll/Extensions/ServiceCollectionExtensions.cs diff --git a/Server.Bll/Exceptions/CustomException.cs b/Server.Bll/Exceptions/CustomException.cs index be02c96..b0849db 100644 --- a/Server.Bll/Exceptions/CustomException.cs +++ b/Server.Bll/Exceptions/CustomException.cs @@ -1,20 +1,21 @@ -namespace Server.Bll.Exceptions +using Microsoft.AspNetCore.Http; + +namespace Server.Bll.Exceptions; + +public readonly struct CustomException { - public readonly struct CustomException - { - public int Code { get;} - public string Message { get; } + public int Code { get;} + public string Message { get; } - public CustomException(string template, int code = 400) - { - Message = template; - Code = code; - } + public CustomException(string template, int code = StatusCodes.Status400BadRequest) + { + Message = template; + Code = code; + } - public CustomException(string template, string customObject, int code = 400) - { - Message = string.Format(template, customObject); - Code = code; - } + public CustomException(string template, string customObject, int code = StatusCodes.Status400BadRequest) + { + Message = string.Format(template, customObject); + Code = code; } } \ No newline at end of file diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index 2d768b1..e4634d7 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -1,21 +1,20 @@ -namespace Server.Bll.Exceptions +namespace Server.Bll.Exceptions; + +public static class ExceptionTemplates { - public static class ExceptionTemplates - { - // GENERAL EXCEPTION MESSAGES - public const string Unknown = "Unknown error occured. Please try again"; - public const string NotAllowed = "You are not allowed to do this."; + // GENERAL EXCEPTION MESSAGES + public const string Unknown = "Unknown error occured. Please try again"; + public const string NotAllowed = "You are not allowed to do this."; - // ROOM EXCEPTIONS - public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; - public const string RoomNotExists = "Room does not exist."; - public const string RoomFull = "This room is full."; - public const string AlreadyInRoom = "You are already in room."; - public const string RoomNotFull = "Room is not full."; - public const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; + // ROOM EXCEPTIONS + public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; + public const string RoomNotExists = "Room does not exist."; + public const string RoomFull = "This room is full."; + public const string AlreadyInRoom = "You are already in room."; + public const string RoomNotFull = "Room is not full."; + public const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; - // ROUND EXCEPTIONS - public const string RoundAlreadyCreated = "Round is already creaded."; - public const string RoundNotFound = "Round with id \"{0}\" is not found"; - } + // ROUND EXCEPTIONS + public const string RoundAlreadyCreated = "Round is already creaded."; + public const string RoundNotFound = "Round with id \"{0}\" is not found"; } \ No newline at end of file diff --git a/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server.Bll/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..40db26d --- /dev/null +++ b/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; +using Server.Bll.HostedServices; +using Server.Bll.Services; +using Server.Bll.Services.Interfaces; + +namespace Server.Bll.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddBusinessLogic(this IServiceCollection service) + { + service + .AddTransient() + .AddTransient() + .AddHostedService(); + service.AddHttpContextAccessor(); + + // In this way I am registering multiple interfaces to one Transient instance of RoomService; + service + .AddTransient() + .AddTransient(provider => provider.GetRequiredService()) + .AddTransient(provider => provider.GetRequiredService()); + + service + .AddTransient(); + + return service; + } +} \ No newline at end of file diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index d651dcd..c5d909c 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -4,55 +4,53 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Server.Bll.Services; using Server.Bll.Services.Interfaces; -namespace Server.Bll.HostedServices +namespace Server.Bll.HostedServices; + +public sealed class CleanerHostedService : IHostedService { - public class CleanerHostedService : IHostedService - { - private readonly IServiceScopeFactory _serviceProvider; - private readonly ILogger _logger; - private Timer _timer; + private readonly IServiceScopeFactory _serviceProvider; + private readonly ILogger _logger; + private Timer _timer; - // todo: options of max time + // todo: options of max time - public CleanerHostedService( - ILogger logger, - IServiceScopeFactory serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } + public CleanerHostedService( + ILogger logger, + IServiceScopeFactory serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + } - public Task StartAsync(CancellationToken cancellationToken) - { - _timer = new Timer( - CleanJunk, - _serviceProvider, - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(10)); - return Task.CompletedTask; - } + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer( + CleanJunk, + _serviceProvider, + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(10)); + return Task.CompletedTask; + } - private async void CleanJunk(object state) - { - _logger.LogInformation("Starting Cleaning"); - var factory = (IServiceScopeFactory) state; - using var scope = factory.CreateScope(); - var roomService = scope.ServiceProvider.GetRequiredService(); + private async void CleanJunk(object state) + { + _logger.LogInformation("Starting Cleaning"); + var factory = (IServiceScopeFactory) state; + using var scope = factory.CreateScope(); + var roomService = scope.ServiceProvider.GetRequiredService(); - var rooms = await roomService - .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); + var rooms = await roomService + .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); - _logger.LogInformation("Cleaned {0} entities", rooms.ToString()); - } + _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); + } - public Task StopAsync(CancellationToken cancellationToken) - { - _timer?.Dispose(); + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Dispose(); - return Task.CompletedTask; - } + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Server.Bll/Models/AccountModel.cs b/Server.Bll/Models/AccountModel.cs index a8e5e12..bc20df6 100644 --- a/Server.Bll/Models/AccountModel.cs +++ b/Server.Bll/Models/AccountModel.cs @@ -1,8 +1,7 @@ -namespace Server.Bll.Models +namespace Server.Bll.Models; + +public sealed class AccountModel { - public class AccountModel - { - public string Login { get; set; } - } + public string Login { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs index 3b28746..12d9648 100644 --- a/Server.Bll/Models/RoomModel.cs +++ b/Server.Bll/Models/RoomModel.cs @@ -1,25 +1,24 @@ using System; -using Mapster; -namespace Server.Bll.Models +namespace Server.Bll.Models; + +public sealed class RoomModel { - public class RoomModel - { - public int Id { get; set; } - public int? RoundId { get; set; } + public int Id { get; set; } + + public int? RoundId { get; set; } - public string RoomCode { get; set; } + public string RoomCode { get; set; } - public RoomPlayersModel RoomPlayers { get; set; } + public RoomPlayersModel RoomPlayers { get; set; } - public bool IsPrivate { get; set; } + public bool IsPrivate { get; set; } - public bool IsReady { get; set; } + public bool IsReady { get; set; } - public bool IsFull { get; set; } + public bool IsFull { get; set; } - public DateTimeOffset CreationTime { get; set; } + public DateTimeOffset CreationTime { get; set; } - public bool IsRoundEnded { get; set; } - } + public bool IsRoundEnded { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomPlayersModel.cs b/Server.Bll/Models/RoomPlayersModel.cs index bf82c30..7d4bb2e 100644 --- a/Server.Bll/Models/RoomPlayersModel.cs +++ b/Server.Bll/Models/RoomPlayersModel.cs @@ -1,11 +1,13 @@  -namespace Server.Bll.Models -{ - public class RoomPlayersModel - { - public AccountModel FirstPlayer { get; set; } - public int FirstPlayerMove { get; set; } - public AccountModel SecondPlayer { get; set; } - public int SecondPlayerMove { get; set; } - } +namespace Server.Bll.Models; + +public sealed class RoomPlayersModel +{ + public AccountModel FirstPlayer { get; set; } + + public int FirstPlayerMove { get; set; } + + public AccountModel SecondPlayer { get; set; } + + public int SecondPlayerMove { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/RoundModel.cs b/Server.Bll/Models/RoundModel.cs index fd7514c..e9a01ba 100644 --- a/Server.Bll/Models/RoundModel.cs +++ b/Server.Bll/Models/RoundModel.cs @@ -1,15 +1,20 @@ using System; -namespace Server.Bll.Models +namespace Server.Bll.Models; + +public sealed class RoundModel { - public class RoundModel - { - public int Id { get; set; } - public bool IsFinished { get; set; } - public DateTimeOffset TimeFinished { get; set; } - public AccountModel Winner { get; set; } - public AccountModel Loser { get; set; } - public int FirstPlayerMove { get; set; } - public int SecondPlayerMove { get; set; } - } + public int Id { get; set; } + + public bool IsFinished { get; set; } + + public DateTimeOffset TimeFinished { get; set; } + + public AccountModel Winner { get; set; } + + public AccountModel Loser { get; set; } + + public int FirstPlayerMove { get; set; } + + public int SecondPlayerMove { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/StatisticsModel.cs b/Server.Bll/Models/StatisticsModel.cs index 6014b52..b3783a2 100644 --- a/Server.Bll/Models/StatisticsModel.cs +++ b/Server.Bll/Models/StatisticsModel.cs @@ -1,53 +1,52 @@ -namespace Server.Bll.Models +namespace Server.Bll.Models; + +public sealed class StatisticsModel { - public class StatisticsModel - { - public AccountModel Account { get; set; } - - /// - /// Total amount of Wins - /// - public int? Wins { get; set; } - - /// - /// Total amount of Loses - /// - public int? Loss { get; set; } - - /// - /// Total amount of Draws. OBSOLETE - /// - - public int? Draws { get; set; } - - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - public double? WinLossRatio { get; set; } - - /// - /// Ratio for the last 7 days - /// - public string TimeSpent { get; set; } - - /// - /// Times used rock - /// - public int? UsedRock { get; set; } - - /// - /// Times used Paper - /// - public int? UsedPaper { get; set; } - - /// - /// Times used Scissors - /// - public int? UsedScissors { get; set; } - - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - public int? Score { get; set; } - } + public AccountModel Account { get; set; } + + /// + /// Total amount of Wins + /// + public int? Wins { get; set; } + + /// + /// Total amount of Loses + /// + public int? Loss { get; set; } + + /// + /// Total amount of Draws. OBSOLETE + /// + + public int? Draws { get; set; } + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + public double? WinLossRatio { get; set; } + + /// + /// Ratio for the last 7 days + /// + public string TimeSpent { get; set; } + + /// + /// Times used rock + /// + public int? UsedRock { get; set; } + + /// + /// Times used Paper + /// + public int? UsedPaper { get; set; } + + /// + /// Times used Scissors + /// + public int? UsedScissors { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + public int? Score { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IHostedRoomService.cs b/Server.Bll/Services/Interfaces/IHostedRoomService.cs index 0f2f848..ddadfdd 100644 --- a/Server.Bll/Services/Interfaces/IHostedRoomService.cs +++ b/Server.Bll/Services/Interfaces/IHostedRoomService.cs @@ -1,11 +1,9 @@ using System; using System.Threading.Tasks; -using Server.Dal.Entities; -namespace Server.Bll.Services.Interfaces +namespace Server.Bll.Services.Interfaces; + +public interface IHostedRoomService { - public interface IHostedRoomService - { - Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); - } + Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server.Bll/Services/Interfaces/ILongPollingService.cs index 7e71529..01b4589 100644 --- a/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ b/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -1,10 +1,9 @@ using System.Threading.Tasks; -namespace Server.Bll.Services +namespace Server.Bll.Services.Interfaces; + +public interface ILongPollingService { - public interface ILongPollingService - { - Task CheckRoomState(int roomId); - Task CheckRoundState(int roundId); - } + Task CheckRoomState(int roomId); + Task CheckRoundState(int roundId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index cf77a93..08a5e20 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,17 +1,19 @@ -using System; using System.Threading.Tasks; using OneOf; using Server.Bll.Exceptions; using Server.Bll.Models; -namespace Server.Bll.Services.Interfaces +namespace Server.Bll.Services.Interfaces; + +public interface IRoomService { - public interface IRoomService - { - Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false); - Task> JoinRoom(int userId, bool isPrivate, string roomCode); - Task> GetRoom(int roomId); - Task UpdateRoom(RoomModel room); - Task DeleteRoom(int userId, int roomId); - } + Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false); + + Task> JoinRoom(int userId, bool isPrivate, string roomCode); + + Task> GetRoom(int roomId); + + Task UpdateRoom(RoomModel room); + + Task DeleteRoom(int userId, int roomId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index 55d9839..d7b64e6 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -3,12 +3,11 @@ using OneOf; using Server.Bll.Exceptions; -namespace Server.Bll.Services.Interfaces +namespace Server.Bll.Services.Interfaces; + +public interface IRoundService { - public interface IRoundService - { - Task> CreateRoundAsync(int userId, int roomId); - Task MakeMoveAsync(); - Task> UpdateRoundAsync(int userId, RoundModel roundModel); - } + Task> CreateRoundAsync(int userId, int roomId); + Task MakeMoveAsync(); + Task> UpdateRoundAsync(int userId, RoundModel roundModel); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs index e8b4050..cfea871 100644 --- a/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ b/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -2,11 +2,10 @@ using System.Threading.Tasks; using Server.Bll.Models; -namespace Server.Bll.Services.Interfaces +namespace Server.Bll.Services.Interfaces; + +public interface IStatisticsService { - public interface IStatisticsService - { - Task> GetAllStatistics(); - Task GetPersonalStatistics(int userId); - } + Task> GetAllStatistics(); + Task GetPersonalStatistics(int userId); } \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index 80132f2..be670d8 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -2,29 +2,28 @@ using Server.Bll.Services.Interfaces; using Server.Dal.Context; -namespace Server.Bll.Services +namespace Server.Bll.Services; + +internal sealed class LongPollingService : ILongPollingService { - public class LongPollingService : ILongPollingService - { - private readonly ServerContext _serverContext; + private readonly ServerContext _serverContext; - public LongPollingService(ServerContext serverContext) - { - _serverContext = serverContext; - } + public LongPollingService(ServerContext serverContext) + { + _serverContext = serverContext; + } - public async Task CheckRoomState(int roomId) - { - var thisRoom = await _serverContext.Rooms.FindAsync(roomId.ToString()); + public async Task CheckRoomState(int roomId) + { + var thisRoom = await _serverContext.Rooms.FindAsync(roomId.ToString()); - return thisRoom != null; - } + return thisRoom != null; + } - public async Task CheckRoundState(int roundId) - { - var thisRound = await _serverContext.Rounds.FindAsync(roundId.ToString()); + public async Task CheckRoundState(int roundId) + { + var thisRound = await _serverContext.Rounds.FindAsync(roundId.ToString()); - return thisRound is {WinnerId: null}; - } + return thisRound is {WinnerId: null}; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 8bfb36d..ff1e016 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -10,212 +10,211 @@ using Server.Dal.Context; using Server.Dal.Entities; -namespace Server.Bll.Services +namespace Server.Bll.Services; + +internal sealed class RoomService : IRoomService, IHostedRoomService { - public class RoomService : IRoomService, IHostedRoomService + private readonly DbSet _rooms; + private readonly ServerContext _repository; + private readonly IRoundService _roundService; + + public RoomService(ServerContext repository, + IRoundService roundService) { - private readonly DbSet _rooms; - private readonly ServerContext _repository; - private readonly IRoundService _roundService; + _repository = repository; + _roundService = roundService; + _rooms = _repository.Rooms; + } - public RoomService(ServerContext repository, - IRoundService roundService) + public async Task> + CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) + { + var doesRoomExist = await _repository.RoomPlayersEnumerable + .FirstOrDefaultAsync(x => x.FirstPlayerId == userId + || x.SecondPlayerId == userId); + if (doesRoomExist != null) { - _repository = repository; - _roundService = roundService; - _rooms = _repository.Rooms; + return new CustomException(ExceptionTemplates.TwinkRoom); } - - public async Task> - CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) - { - var doesRoomExist = await _repository.RoomPlayersEnumerable - .FirstOrDefaultAsync(x => x.FirstPlayerId == userId - || x.SecondPlayerId == userId); - if (doesRoomExist != null) - { - return new CustomException(ExceptionTemplates.TwinkRoom); - } - - var room = new Room - { - IsPrivate = isPrivate, - RoomCode = Guid.NewGuid().ToString("n")[..8], - IsFull = false, - CreationTimeTicks = DateTimeOffset.Now.Ticks - }; - - await _rooms.AddAsync(room); - await _repository.SaveChangesAsync(); - var newRoomPlayers = new RoomPlayers - { - RoomId = room.Id, - FirstPlayerId = userId, - PlayersCount = 1 - }; + var room = new Room + { + IsPrivate = isPrivate, + RoomCode = Guid.NewGuid().ToString("n")[..8], + IsFull = false, + CreationTimeTicks = DateTimeOffset.Now.Ticks + }; - if (isTraining) - { - var bot = await _repository.Accounts.FirstAsync(x => x.Login == "BOT"); - newRoomPlayers.SecondPlayerId = bot.Id; - } - - await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); - room.RoomPlayers = newRoomPlayers; - _rooms.Update(room); + await _rooms.AddAsync(room); + await _repository.SaveChangesAsync(); - await _repository.SaveChangesAsync(); + var newRoomPlayers = new RoomPlayers + { + RoomId = room.Id, + FirstPlayerId = userId, + PlayersCount = 1 + }; - return room.Adapt(); + if (isTraining) + { + var bot = await _repository.Accounts.FirstAsync(x => x.Login == "BOT"); + newRoomPlayers.SecondPlayerId = bot.Id; } - - public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) - { - Room thisRoom; - - if (isPrivate) - { - thisRoom = await _rooms - .Include(x => x.RoomPlayers) - .Where(x => !x.IsFull).FirstOrDefaultAsync(); - } - else - { - thisRoom = await _rooms - .Include(x=>x.RoomPlayers). - FirstOrDefaultAsync(x => x.RoomCode == roomCode); - } - - if (thisRoom == null) - { - return new CustomException(ExceptionTemplates.RoomNotExists); - } - - if (thisRoom.RoomPlayers.FirstPlayerId == userId || thisRoom.RoomPlayers.SecondPlayerId == userId) - { - return new CustomException(ExceptionTemplates.AlreadyInRoom); - } - - if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) - { - return new CustomException(ExceptionTemplates.RoomFull); - } - - if (thisRoom.RoomPlayers.FirstPlayerId != 0) - { - thisRoom.RoomPlayers.FirstPlayerId = userId; - _rooms.Update(thisRoom); - - await _repository.SaveChangesAsync(); - - return thisRoom.Adapt(); - } - if (thisRoom.RoomPlayers.SecondPlayerId == 0) - { - return new CustomException(ExceptionTemplates.Unknown); - } + await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); + room.RoomPlayers = newRoomPlayers; + _rooms.Update(room); - thisRoom.RoomPlayers.SecondPlayerId = userId; + await _repository.SaveChangesAsync(); + + return room.Adapt(); + } + + public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) + { + Room thisRoom; + + if (isPrivate) + { + thisRoom = await _rooms + .Include(x => x.RoomPlayers) + .Where(x => !x.IsFull).FirstOrDefaultAsync(); + } + else + { + thisRoom = await _rooms + .Include(x=>x.RoomPlayers). + FirstOrDefaultAsync(x => x.RoomCode == roomCode); + } + + if (thisRoom == null) + { + return new CustomException(ExceptionTemplates.RoomNotExists); + } - if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) - { - await _roundService.CreateRoundAsync(userId, thisRoom.Id); - } + if (thisRoom.RoomPlayers.FirstPlayerId == userId || thisRoom.RoomPlayers.SecondPlayerId == userId) + { + return new CustomException(ExceptionTemplates.AlreadyInRoom); + } + + if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + { + return new CustomException(ExceptionTemplates.RoomFull); + } + if (thisRoom.RoomPlayers.FirstPlayerId != 0) + { + thisRoom.RoomPlayers.FirstPlayerId = userId; _rooms.Update(thisRoom); + await _repository.SaveChangesAsync(); + return thisRoom.Adapt(); } - public async Task> GetRoom(int roomId) + if (thisRoom.RoomPlayers.SecondPlayerId == 0) { - var room = await _rooms.FindAsync(roomId.ToString()); + return new CustomException(ExceptionTemplates.Unknown); + } + + thisRoom.RoomPlayers.SecondPlayerId = userId; - return room != null - ? room.Adapt() - : new CustomException(ExceptionTemplates.RoomNotExists); + if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + { + await _roundService.CreateRoundAsync(userId, thisRoom.Id); } + + _rooms.Update(thisRoom); + await _repository.SaveChangesAsync(); + return thisRoom.Adapt(); + } + + public async Task> GetRoom(int roomId) + { + var room = await _rooms.FindAsync(roomId.ToString()); + + return room != null + ? room.Adapt() + : new CustomException(ExceptionTemplates.RoomNotExists); + } - public async Task UpdateRoom(RoomModel room) - { - var thisRoom = await _rooms.FindAsync(room.Id); + public async Task UpdateRoom(RoomModel room) + { + var thisRoom = await _rooms.FindAsync(room.Id); - if ( thisRoom == null) - { - return 400; - } + if ( thisRoom == null) + { + return 400; + } - var updatedRoom = room.Adapt(); + var updatedRoom = room.Adapt(); - thisRoom.IsFull = updatedRoom.IsFull; - thisRoom.IsPrivate = updatedRoom.IsPrivate; - thisRoom.RoundId = updatedRoom.RoundId; + thisRoom.IsFull = updatedRoom.IsFull; + thisRoom.IsPrivate = updatedRoom.IsPrivate; + thisRoom.RoundId = updatedRoom.RoundId; - if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) - { - return 400; - } + if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) + { + return 400; + } - _repository.Update(thisRoom); + _repository.Update(thisRoom); - return 200; - } + return 200; + } - public async Task DeleteRoom(int userId, int roomId) - { - var thisRoom = await _rooms - .Include(x=>x.RoomPlayers) - .FirstOrDefaultAsync(x=>x.Id == roomId); + public async Task DeleteRoom(int userId, int roomId) + { + var thisRoom = await _rooms + .Include(x=>x.RoomPlayers) + .FirstOrDefaultAsync(x=>x.Id == roomId); - if (thisRoom == null) - { - return 400; - } + if (thisRoom == null) + { + return 400; + } - if (thisRoom.RoomPlayers.FirstPlayerId != userId) - { - return 400; - } + if (thisRoom.RoomPlayers.FirstPlayerId != userId) + { + return 400; + } - if (thisRoom.RoomPlayers.SecondPlayerId != userId) - { - return 400; - } + if (thisRoom.RoomPlayers.SecondPlayerId != userId) + { + return 400; + } - _rooms.Remove(thisRoom); + _rooms.Remove(thisRoom); - await _repository.SaveChangesAsync(); + await _repository.SaveChangesAsync(); - return 200; - } + return 200; + } - /// - /// Only to use with I HOSTED SERVICE! - /// - /// - /// - /// - [Obsolete(message:"Should be carefully used")] - public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) - { - var currentDate = DateTimeOffset.Now.Ticks; + /// + /// Only to use with I HOSTED SERVICE! + /// + /// + /// + /// + [Obsolete(message:"Should be carefully used")] + public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) + { + var currentDate = DateTimeOffset.Now.Ticks; - var rooms = await _rooms - .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) - .ToArrayAsync(); + var rooms = await _rooms + .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) + .ToArrayAsync(); - var allRound = await _repository.Rounds - .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) - .ToArrayAsync(); + var allRound = await _repository.Rounds + .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) + .ToArrayAsync(); - _rooms.RemoveRange(rooms); - _repository.Rounds.RemoveRange(allRound); + _rooms.RemoveRange(rooms); + _repository.Rounds.RemoveRange(allRound); - await _repository.SaveChangesAsync(); + await _repository.SaveChangesAsync(); - return rooms.Length + allRound.Length; - } + return rooms.Length + allRound.Length; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index daed2aa..8134127 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -10,93 +10,92 @@ using OneOf; using Server.Dal.Entities; -namespace Server.Bll.Services +namespace Server.Bll.Services; + +internal sealed class RoundService : IRoundService { - public class RoundService : IRoundService - { - private readonly ServerContext _serverContext; + private readonly ServerContext _serverContext; - public RoundService(ServerContext serverContext) + public RoundService(ServerContext serverContext) + { + _serverContext = serverContext; + } + public async Task> CreateRoundAsync(int userId, int roomId) + { + var foundRoom = await _serverContext + .Rooms + .Include(x => x.RoomPlayers) + .FirstOrDefaultAsync(x => x.Id == roomId); + if (foundRoom is null) + return new CustomException(ExceptionTemplates.RoomNotExists); + if (foundRoom.RoomPlayers.FirstPlayerId != userId) { - _serverContext = serverContext; + if(foundRoom.RoomPlayers.SecondPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); + } - public async Task> CreateRoundAsync(int userId, int roomId) + if (foundRoom.RoomPlayers.SecondPlayerId != userId) { - var foundRoom = await _serverContext - .Rooms - .Include(x => x.RoomPlayers) - .FirstOrDefaultAsync(x => x.Id == roomId); - if (foundRoom is null) - return new CustomException(ExceptionTemplates.RoomNotExists); - if (foundRoom.RoomPlayers.FirstPlayerId != userId) - { - if(foundRoom.RoomPlayers.SecondPlayerId != userId) - return new CustomException(ExceptionTemplates.NotAllowed); - - } - if (foundRoom.RoomPlayers.SecondPlayerId != userId) - { - if(foundRoom.RoomPlayers.FirstPlayerId != userId) - return new CustomException(ExceptionTemplates.NotAllowed); - } + if(foundRoom.RoomPlayers.FirstPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); + } - if (!foundRoom.IsFull) - return new CustomException(ExceptionTemplates.RoomNotFull); + if (!foundRoom.IsFull) + return new CustomException(ExceptionTemplates.RoomNotFull); - var newRound = new Round - { - RoomPlayersId = foundRoom.RoomPlayers.Id, - FirstPlayerMove = 0, - SecondPlayerMove = 0, - LastMoveTicks = DateTimeOffset.Now.Ticks, - TimeFinishedTicks = 0, - IsFinished = false - }; + var newRound = new Round + { + RoomPlayersId = foundRoom.RoomPlayers.Id, + FirstPlayerMove = 0, + SecondPlayerMove = 0, + LastMoveTicks = DateTimeOffset.Now.Ticks, + TimeFinishedTicks = 0, + IsFinished = false + }; - await _serverContext.AddAsync(newRound); + await _serverContext.AddAsync(newRound); - foundRoom.RoundId = newRound.Id; - await _serverContext.SaveChangesAsync(); + foundRoom.RoundId = newRound.Id; + await _serverContext.SaveChangesAsync(); - return newRound.Adapt(); - } + return newRound.Adapt(); + } - [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] - public Task MakeMoveAsync() - { - throw new NotImplementedException(); - } + [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] + public Task MakeMoveAsync() + { + throw new NotImplementedException(); + } - public async Task> UpdateRoundAsync(int userId, RoundModel roundModel) - { - var thisRound = await _serverContext - .Rounds - .Include(x => x.RoomPlayers) - .ThenInclude(x=>x.Room) - .FirstOrDefaultAsync(x => x.Id == roundModel.Id); + public async Task> UpdateRoundAsync(int userId, RoundModel roundModel) + { + var thisRound = await _serverContext + .Rounds + .Include(x => x.RoomPlayers) + .ThenInclude(x=>x.Room) + .FirstOrDefaultAsync(x => x.Id == roundModel.Id); - if(thisRound is null) - return new CustomException(ExceptionTemplates.RoundNotFound, roundModel.Id); - if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) - return new CustomException(ExceptionTemplates.NotAllowed); + if(thisRound is null) + return new CustomException(ExceptionTemplates.RoundNotFound, roundModel.Id); + if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) + return new CustomException(ExceptionTemplates.NotAllowed); - var incomeRound = roundModel.Adapt(); - thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; - thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; - thisRound.LastMoveTicks = incomeRound.LastMoveTicks; + var incomeRound = roundModel.Adapt(); + thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; + thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; + thisRound.LastMoveTicks = incomeRound.LastMoveTicks; - if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) - { - thisRound.IsFinished = true; - thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; - } + if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) + { + thisRound.IsFinished = true; + thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; + } - if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) - return new CustomException(ExceptionTemplates.NotAllowed); + if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) + return new CustomException(ExceptionTemplates.NotAllowed); - _serverContext.Update(thisRound); - await _serverContext.SaveChangesAsync(); - return thisRound.Adapt(); - } + _serverContext.Update(thisRound); + await _serverContext.SaveChangesAsync(); + return thisRound.Adapt(); } } \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index aa604ed..70daeb8 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -6,30 +6,29 @@ using Server.Bll.Services.Interfaces; using Server.Dal.Context; -namespace Server.Bll.Services +namespace Server.Bll.Services; + +internal sealed class StatisticsService : IStatisticsService { - public class StatisticsService : IStatisticsService - { - private readonly ServerContext _repository; + private readonly ServerContext _repository; - public StatisticsService(ServerContext repository) - { - _repository = repository; - } + public StatisticsService(ServerContext repository) + { + _repository = repository; + } - public async Task> GetAllStatistics() - { - return await _repository.StatisticsEnumerable.ProjectToType() - .ToArrayAsync(); - } + public async Task> GetAllStatistics() + { + return await _repository.StatisticsEnumerable.ProjectToType() + .ToArrayAsync(); + } - public async Task GetPersonalStatistics(int userId) - { - var statistics = await _repository.StatisticsEnumerable - .Include(x=>x.Account) - .FirstOrDefaultAsync(x=>x.Id == userId); + public async Task GetPersonalStatistics(int userId) + { + var statistics = await _repository.StatisticsEnumerable + .Include(x=>x.Account) + .FirstOrDefaultAsync(x=>x.Id == userId); - return statistics.Adapt(); - } + return statistics.Adapt(); } } \ No newline at end of file diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index 861b09e..2cb36d7 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -46,6 +46,5 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasOne(x => x.RoomPlayers) .WithOne() .OnDelete(DeleteBehavior.NoAction); - } } \ No newline at end of file diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index b5b27e0..ba8f876 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Server.Bll.Services; +using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index b654fae..9c51c01 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -31,6 +31,7 @@ public async Task Invoke(HttpContext context) $"Path:{context.Request.Path}\n" + $"QueryString:{context.Request.QueryString}\n" + $"Request Body:{await ObtainRequestBody(context.Request)}\n"; + _logger.LogWarning(requestInformation); var originalResponseBody = context.Response.Body; diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs index f71979c..8f8ec6a 100644 --- a/Server.Host/Extensions/ServiceExtension.cs +++ b/Server.Host/Extensions/ServiceExtension.cs @@ -1,7 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Server.Bll.HostedServices; -using Server.Bll.Services; -using Server.Bll.Services.Interfaces; +using Server.Bll.Extensions; namespace Server.Host.Extensions; @@ -9,21 +7,8 @@ public static class ServiceExtension { public static IServiceCollection AddServices(this IServiceCollection service) { - service - .AddTransient() - .AddTransient() - .AddHostedService(); - service.AddHttpContextAccessor(); - - // In this way I am registering multiple interfaces to one Transient instance of RoomService; - service - .AddTransient() - .AddTransient(provider => provider.GetRequiredService()) - .AddTransient(provider => provider.GetRequiredService()); - - service - .AddTransient(); - + service.AddBusinessLogic(); + return service; } } \ No newline at end of file From 1b63782dbf192d9901197f18b9f862ae30b12e72 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 1 May 2022 23:36:21 +0300 Subject: [PATCH 30/56] More fixes. Sealing, cleaning. Todo: refactor logic and tests --- .../Exceptions/UserException.cs | 4 +- .../Exceptions/UserExceptionsTemplates.cs | 16 +++- .../Extensions/AuthenticationExtension.cs | 3 +- .../Services/AttemptValidationService.cs | 1 + Server.Authentication/Services/AuthService.cs | 23 +++--- Server.Bll/Exceptions/ExceptionTemplates.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 9 +-- .../HostedServices/CleanerHostedService.cs | 6 +- .../Services/Interfaces/IHostedRoomService.cs | 9 --- .../Services/Interfaces/IRoomService.cs | 3 + Server.Bll/Services/LongPollingService.cs | 2 +- Server.Bll/Services/RoomService.cs | 74 +++++++++---------- Server.Bll/Services/RoundService.cs | 2 +- Server.Bll/Services/StatisticsService.cs | 4 +- Server.Dal/Extensions/SeedingExtension.cs | 3 +- 15 files changed, 82 insertions(+), 79 deletions(-) delete mode 100644 Server.Bll/Services/Interfaces/IHostedRoomService.cs diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs index 092222b..d9a3f1f 100644 --- a/Server.Authentication/Exceptions/UserException.cs +++ b/Server.Authentication/Exceptions/UserException.cs @@ -8,10 +8,10 @@ public readonly struct UserException public int Code { get; } public string Message { get; } - public UserException(string messageType, string message, DateTimeOffset dateTimeOffset) + public UserException(string message) { Code = StatusCodes.Status400BadRequest; - Message = string.Format(messageType, message, dateTimeOffset.ToString("f")); + Message = message; } public UserException(string invalidCredentialsForUser, string loginName) diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs index e8e6cf7..27730f0 100644 --- a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs +++ b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -1,10 +1,18 @@ +using System; + namespace Server.Authentication.Exceptions; internal static class UserExceptionsTemplates { - internal const string UserCoolDown = "User [{0}] has been cooled down till [{1}]."; - internal const string UserInvalidCredentials = "Invalid Crenedtials for user [{0}]."; - internal const string UserNotFound = "User with login [{0}] is not found."; - internal const string UserAlreadyExists = "User with login [{0}] already exists."; + internal static string UserCoolDown(this string userName, DateTimeOffset date) => + $"User [{userName}] has been cooled down till [{date}]."; + + internal static string UserInvalidCredentials(this string userCredentials) => + $"Invalid Credentials for user [{userCredentials}]."; + + internal static string UserNotFound(this string userName) => $"User with login [{userName}] is not found."; + + internal static string UserAlreadyExists(this string userName) => $"User with login [{userName}] already exists."; + internal const string UnknownError = "Unknown error occured. Please try again."; } \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index be46318..b61669e 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -21,7 +21,7 @@ public static class AuthenticationExtension /// public static IServiceCollection AddAuthentications(this IServiceCollection services) { - if (services == null) throw new ArgumentNullException(nameof(services)); + _ = services ?? throw new ArgumentNullException(nameof(services)); services.AddOptions(); @@ -53,6 +53,7 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv .AddTransient() .AddTransient() .AddSingleton(typeof(AttemptValidationService)); + return services; } } \ No newline at end of file diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 3738b17..3443a7a 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -18,6 +18,7 @@ public bool InsertFailAttempt(string userId) // todo: in options _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); _failedAttempts.TryRemove(userId, out _); + return true; } } diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index eb4bb37..2f0025b 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -47,21 +48,21 @@ public async Task> { if (string.IsNullOrWhiteSpace(login)) { - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(login)); + return new UserException(nameof(login).UserInvalidCredentials()); } if (string.IsNullOrEmpty(password)) { - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, nameof(password)); + return new UserException(nameof(password).UserInvalidCredentials()); } var release = await _semaphore.WaitAsync(1000); try { - if (await _accounts.CountAsync(x=>x.Login == login) > 0) + if (await _accounts.AnyAsync(account => account.Login.Equals(login, StringComparison.OrdinalIgnoreCase))) { - return new UserException(UserExceptionsTemplates.UserAlreadyExists, login); + return new UserException(login.UserAlreadyExists()); } var account = new Account @@ -81,7 +82,7 @@ public async Task> await _repository.SaveChangesAsync(); - return 200; + return StatusCodes.Status200OK; } catch { @@ -91,24 +92,26 @@ public async Task> } finally { - if (release) + if (release) + { _semaphore.Release(); + } } } /// public async Task> LoginAsync(string login, string password) { - var userAccount = await _accounts.FirstOrDefaultAsync(x => x.Login == login); + var userAccount = await _accounts.FirstOrDefaultAsync(account => account.Login == login); if (userAccount is null) { - return new UserException(UserExceptionsTemplates.UserNotFound,login); + return new UserException(login.UserNotFound()); } if (_attemptValidationService.IsCoolDown(login, out var coolRequestDate)) { - return new UserException(UserExceptionsTemplates.UserCoolDown, login, coolRequestDate); + return new UserException(login.UserCoolDown(coolRequestDate)); } if (userAccount.Password.IsHashEqual(login)) @@ -122,7 +125,7 @@ public async Task> LoginAsync(string lo _attemptValidationService.InsertFailAttempt(login); - return new UserException(UserExceptionsTemplates.UserInvalidCredentials, login); + return new UserException(login.UserInvalidCredentials()); } public Task RemoveAsync(int accountId) diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index e4634d7..3019b07 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -16,5 +16,5 @@ public static class ExceptionTemplates // ROUND EXCEPTIONS public const string RoundAlreadyCreated = "Round is already creaded."; - public const string RoundNotFound = "Round with id \"{0}\" is not found"; + public static string RoundNotFound(int roundId) => $"Round with id \"{roundId}\" is not found"; } \ No newline at end of file diff --git a/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server.Bll/Extensions/ServiceCollectionExtensions.cs index 40db26d..3676d6b 100644 --- a/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -14,14 +14,9 @@ public static IServiceCollection AddBusinessLogic(this IServiceCollection servic .AddTransient() .AddHostedService(); service.AddHttpContextAccessor(); - - // In this way I am registering multiple interfaces to one Transient instance of RoomService; - service - .AddTransient() - .AddTransient(provider => provider.GetRequiredService()) - .AddTransient(provider => provider.GetRequiredService()); - + service + .AddTransient() .AddTransient(); return service; diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index c5d909c..2d032a5 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -31,16 +31,18 @@ public Task StartAsync(CancellationToken cancellationToken) _serviceProvider, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10)); + return Task.CompletedTask; } private async void CleanJunk(object state) { _logger.LogInformation("Starting Cleaning"); + var factory = (IServiceScopeFactory) state; using var scope = factory.CreateScope(); - var roomService = scope.ServiceProvider.GetRequiredService(); - + var roomService = scope.ServiceProvider.GetRequiredService(); + //todo: timespan to option. var rooms = await roomService .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); diff --git a/Server.Bll/Services/Interfaces/IHostedRoomService.cs b/Server.Bll/Services/Interfaces/IHostedRoomService.cs deleted file mode 100644 index ddadfdd..0000000 --- a/Server.Bll/Services/Interfaces/IHostedRoomService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Server.Bll.Services.Interfaces; - -public interface IHostedRoomService -{ - Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 08a5e20..8b54f1d 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using OneOf; using Server.Bll.Exceptions; @@ -9,6 +10,8 @@ public interface IRoomService { Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false); + Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); + Task> JoinRoom(int userId, bool isPrivate, string roomCode); Task> GetRoom(int roomId); diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index be670d8..6418136 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -17,7 +17,7 @@ public async Task CheckRoomState(int roomId) { var thisRoom = await _serverContext.Rooms.FindAsync(roomId.ToString()); - return thisRoom != null; + return thisRoom is not null; } public async Task CheckRoundState(int roundId) diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index ff1e016..c8aec8f 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Mapster; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using OneOf; using Server.Bll.Exceptions; @@ -12,7 +13,7 @@ namespace Server.Bll.Services; -internal sealed class RoomService : IRoomService, IHostedRoomService +internal sealed class RoomService : IRoomService { private readonly DbSet _rooms; private readonly ServerContext _repository; @@ -30,9 +31,9 @@ public async Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable - .FirstOrDefaultAsync(x => x.FirstPlayerId == userId - || x.SecondPlayerId == userId); - if (doesRoomExist != null) + .FirstOrDefaultAsync(roomPlayers => roomPlayers.FirstPlayerId == userId + || roomPlayers.SecondPlayerId == userId); + if (doesRoomExist is not null) { return new CustomException(ExceptionTemplates.TwinkRoom); } @@ -57,7 +58,7 @@ public async Task> if (isTraining) { - var bot = await _repository.Accounts.FirstAsync(x => x.Login == "BOT"); + var bot = await _repository.Accounts.FirstAsync(account => account.Login.Equals("BOT", StringComparison.OrdinalIgnoreCase)); newRoomPlayers.SecondPlayerId = bot.Id; } @@ -72,22 +73,16 @@ public async Task> public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) { - Room thisRoom; - - if (isPrivate) - { - thisRoom = await _rooms - .Include(x => x.RoomPlayers) - .Where(x => !x.IsFull).FirstOrDefaultAsync(); - } - else - { - thisRoom = await _rooms - .Include(x=>x.RoomPlayers). - FirstOrDefaultAsync(x => x.RoomCode == roomCode); - } - - if (thisRoom == null) + var thisRoom = isPrivate + ? await _rooms + .Include(room => room.RoomPlayers) + .Where(room => !room.IsFull) + .FirstOrDefaultAsync() + : await _rooms + .Include(room=>room.RoomPlayers). + FirstOrDefaultAsync(room => room.RoomCode == roomCode); + + if (thisRoom is null) { return new CustomException(ExceptionTemplates.RoomNotExists); } @@ -97,12 +92,12 @@ public async Task> JoinRoom(int userId, bool i return new CustomException(ExceptionTemplates.AlreadyInRoom); } - if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + if (thisRoom.RoomPlayers.FirstPlayerId is not 0 && thisRoom.RoomPlayers.SecondPlayerId is not 0) { return new CustomException(ExceptionTemplates.RoomFull); } - if (thisRoom.RoomPlayers.FirstPlayerId != 0) + if (thisRoom.RoomPlayers.FirstPlayerId is not 0) { thisRoom.RoomPlayers.FirstPlayerId = userId; _rooms.Update(thisRoom); @@ -112,28 +107,29 @@ public async Task> JoinRoom(int userId, bool i return thisRoom.Adapt(); } - if (thisRoom.RoomPlayers.SecondPlayerId == 0) + if (thisRoom.RoomPlayers.SecondPlayerId is 0) { return new CustomException(ExceptionTemplates.Unknown); } thisRoom.RoomPlayers.SecondPlayerId = userId; - if (thisRoom.RoomPlayers.FirstPlayerId != 0 && thisRoom.RoomPlayers.SecondPlayerId != 0) + if (thisRoom.RoomPlayers.FirstPlayerId is not 0 && thisRoom.RoomPlayers.SecondPlayerId is not 0) { await _roundService.CreateRoundAsync(userId, thisRoom.Id); } _rooms.Update(thisRoom); await _repository.SaveChangesAsync(); + return thisRoom.Adapt(); } public async Task> GetRoom(int roomId) { - var room = await _rooms.FindAsync(roomId.ToString()); + var room = await _rooms.FindAsync(roomId); - return room != null + return room is not null ? room.Adapt() : new CustomException(ExceptionTemplates.RoomNotExists); } @@ -142,9 +138,9 @@ public async Task> GetRoom(int roomId) { var thisRoom = await _rooms.FindAsync(room.Id); - if ( thisRoom == null) + if (thisRoom is null) { - return 400; + return StatusCodes.Status400BadRequest; } var updatedRoom = room.Adapt(); @@ -155,40 +151,40 @@ public async Task> GetRoom(int roomId) if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) { - return 400; + return StatusCodes.Status400BadRequest; } _repository.Update(thisRoom); - return 200; + return StatusCodes.Status200OK; } public async Task DeleteRoom(int userId, int roomId) { var thisRoom = await _rooms - .Include(x=>x.RoomPlayers) - .FirstOrDefaultAsync(x=>x.Id == roomId); + .Include(room=>room.RoomPlayers) + .FirstOrDefaultAsync(room => room.Id == roomId); - if (thisRoom == null) + if (thisRoom is null) { - return 400; + return StatusCodes.Status400BadRequest; } if (thisRoom.RoomPlayers.FirstPlayerId != userId) { - return 400; + return StatusCodes.Status400BadRequest; } if (thisRoom.RoomPlayers.SecondPlayerId != userId) { - return 400; + return StatusCodes.Status400BadRequest; } _rooms.Remove(thisRoom); await _repository.SaveChangesAsync(); - return 200; + return StatusCodes.Status200OK; } /// @@ -203,7 +199,7 @@ public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan ro var currentDate = DateTimeOffset.Now.Ticks; var rooms = await _rooms - .Where(x => x.CreationTimeTicks + roomOutDate.Ticks < currentDate && x.RoundId == null) + .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.RoundId == null) .ToArrayAsync(); var allRound = await _repository.Rounds diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index 8134127..a1e4788 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -76,7 +76,7 @@ public async Task> UpdateRoundAsync(int userId .FirstOrDefaultAsync(x => x.Id == roundModel.Id); if(thisRound is null) - return new CustomException(ExceptionTemplates.RoundNotFound, roundModel.Id); + return new CustomException(ExceptionTemplates.RoundNotFound(roundModel.Id)); if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) return new CustomException(ExceptionTemplates.NotAllowed); diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index 70daeb8..e981d11 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -19,7 +19,9 @@ public StatisticsService(ServerContext repository) public async Task> GetAllStatistics() { - return await _repository.StatisticsEnumerable.ProjectToType() + return await _repository + .StatisticsEnumerable + .ProjectToType() .ToArrayAsync(); } diff --git a/Server.Dal/Extensions/SeedingExtension.cs b/Server.Dal/Extensions/SeedingExtension.cs index a0b458d..9572097 100644 --- a/Server.Dal/Extensions/SeedingExtension.cs +++ b/Server.Dal/Extensions/SeedingExtension.cs @@ -13,7 +13,8 @@ public static class SeedingExtension public static async Task EnsureBotCreated(this ServerContext context) { var bot = await context.Accounts.FirstOrDefaultAsync(account => account.Login.Equals(DefaultName, StringComparison.OrdinalIgnoreCase)); - if (bot == null) + + if (bot is null) await context.AddAsync(new Account { Id = 0, From c60177a9a6816056a543884ebf9ae9934bc961b2 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Mon, 2 May 2022 12:03:58 +0300 Subject: [PATCH 31/56] Fixed AuthService.cs --- .../Exceptions/UserExceptionsTemplates.cs | 8 ++++---- Server.Authentication/Services/AuthService.cs | 12 ++++++++---- Server.Host/Contracts/AccountDto.cs | 2 +- Server.Host/Controllers/AccountController.cs | 11 ++++++++--- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs index 27730f0..ca99e76 100644 --- a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs +++ b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -5,14 +5,14 @@ namespace Server.Authentication.Exceptions; internal static class UserExceptionsTemplates { internal static string UserCoolDown(this string userName, DateTimeOffset date) => - $"User [{userName}] has been cooled down till [{date}]."; + $"User [{userName}] has been cooled down till \"{date}\"."; internal static string UserInvalidCredentials(this string userCredentials) => - $"Invalid Credentials for user [{userCredentials}]."; + $"Invalid Credentials for user \"{userCredentials}\"."; - internal static string UserNotFound(this string userName) => $"User with login [{userName}] is not found."; + internal static string UserNotFound(this string userName) => $"User with login \"{userName}\" is not found."; - internal static string UserAlreadyExists(this string userName) => $"User with login [{userName}] already exists."; + internal static string UserAlreadyExists(this string userName) => $"User with login \"{userName}\" already exists."; internal const string UnknownError = "Unknown error occured. Please try again."; } \ No newline at end of file diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 2f0025b..93eed13 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -60,7 +60,7 @@ public async Task> try { - if (await _accounts.AnyAsync(account => account.Login.Equals(login, StringComparison.OrdinalIgnoreCase))) + if (await _accounts.AnyAsync(account => account.Login.Equals(login))) { return new UserException(login.UserAlreadyExists()); } @@ -68,18 +68,22 @@ public async Task> var account = new Account { Login = login, - Password = password.EncodeBase64() + Password = password.EncodeBase64(), }; await _accounts.AddAsync(account); await _repository.SaveChangesAsync(); - + var accountStatistics = new Statistics { AccountId = account.Id }; + await _repository.StatisticsEnumerable.AddAsync(accountStatistics); - + await _repository.SaveChangesAsync(); + + account.StatisticsId = accountStatistics.Id; + await _repository.SaveChangesAsync(); return StatusCodes.Status200OK; diff --git a/Server.Host/Contracts/AccountDto.cs b/Server.Host/Contracts/AccountDto.cs index d3eed54..af4058f 100644 --- a/Server.Host/Contracts/AccountDto.cs +++ b/Server.Host/Contracts/AccountDto.cs @@ -8,6 +8,6 @@ public sealed class AccountDto public string Login { get; set; } [Required(ErrorMessage = "Password is required!!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] + [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length")] public string Password { get; set;} } \ No newline at end of file diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index f97d2fc..8b13c64 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -20,19 +20,24 @@ public sealed class AccountController : ControllerBase public AccountController(IAuthService authService) { _authService = authService; - } + } + [HttpPost("register")] [AllowAnonymous] [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] public async Task Register(RegisterRequest registerRequest) { - if (!ModelState.IsValid) return BadRequest(); + if (!ModelState.IsValid) + { + return BadRequest(); + } + var newAccount = await _authService .RegisterAsync(registerRequest.Login,registerRequest.Password); return newAccount.Match( - integer => Ok(integer), + _ => Ok(), exception => BadRequest(exception)); } From 10e699a64fe03575d07be0e765db2de5d4a445c5 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 6 May 2022 23:08:55 +0300 Subject: [PATCH 32/56] More bugfixes and refactoring --- .../Exceptions/UserException.cs | 21 ++++++++++++------ .../Extensions/AuthenticationExtension.cs | 5 ++--- .../Models/AccountOutputModel.cs | 10 +++++++++ .../Models/Interfaces/IApplicationUser.cs | 4 ++-- .../Models/Interfaces/IAuthUser.cs | 4 ++-- .../Server.Authentication.csproj | 1 + .../Services/AttemptValidationService.cs | 2 +- Server.Authentication/Services/AuthService.cs | 7 ++---- .../Services/Interfaces/IRoomService.cs | 8 +++---- .../Services/Interfaces/IRoundService.cs | 4 ++-- Server.Bll/Services/RoomService.cs | 11 +++++----- Server.Bll/Services/RoundService.cs | 22 +++++++++++++++++-- Server.Bll/Services/StatisticsService.cs | 4 ++-- Server.Dal/Entities/RoomPlayers.cs | 7 ++++++ Server.Dal/Entities/Round.cs | 12 +++++++++- Server.Dal/Entities/Statistics.cs | 1 - Server.Host/Controllers/RoomController.cs | 8 +++---- Server.Host/Controllers/RoundController.cs | 4 ++-- Server.Host/Extensions/SwaggerExtension.cs | 10 ++++----- 19 files changed, 96 insertions(+), 49 deletions(-) diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs index d9a3f1f..8918bba 100644 --- a/Server.Authentication/Exceptions/UserException.cs +++ b/Server.Authentication/Exceptions/UserException.cs @@ -1,22 +1,29 @@ -using System; using Microsoft.AspNetCore.Http; namespace Server.Authentication.Exceptions; +/// +/// User-related custom structure exception. +/// public readonly struct UserException { + /// + /// Gets response code. + /// public int Code { get; } + + /// + /// Gets exception message. + /// public string Message { get; } + /// + /// Constructor. + /// + /// Exception message. public UserException(string message) { Code = StatusCodes.Status400BadRequest; Message = message; } - - public UserException(string invalidCredentialsForUser, string loginName) - { - Code = StatusCodes.Status400BadRequest; - Message = string.Format(invalidCredentialsForUser, loginName); - } } \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index b61669e..24a32f2 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -10,15 +10,14 @@ namespace Server.Authentication.Extensions; /// -/// Authentication services extension +/// Authentication services extension. /// public static class AuthenticationExtension { /// - /// Register authentication + /// Registers authentication. /// /// Service collection - /// public static IServiceCollection AddAuthentications(this IServiceCollection services) { _ = services ?? throw new ArgumentNullException(nameof(services)); diff --git a/Server.Authentication/Models/AccountOutputModel.cs b/Server.Authentication/Models/AccountOutputModel.cs index 62c3251..cf0d56f 100644 --- a/Server.Authentication/Models/AccountOutputModel.cs +++ b/Server.Authentication/Models/AccountOutputModel.cs @@ -1,7 +1,17 @@ namespace Server.Authentication.Models; +/// +/// Model for output of user account. +/// public sealed class AccountOutputModel { + /// + /// Gets or sets user token (used in header). + /// public string Token { get; set; } + + /// + /// Gets or sets user login. + /// public string Login { get; set; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs index 83e00b9..d4b5742 100644 --- a/Server.Authentication/Models/Interfaces/IApplicationUser.cs +++ b/Server.Authentication/Models/Interfaces/IApplicationUser.cs @@ -1,12 +1,12 @@ namespace Server.Authentication.Models.Interfaces; /// -/// Interface for ApplicationUser class +/// Interface for ApplicationUser class /// public interface IApplicationUser { /// - /// User request id from token + /// Gets user request id from token /// int Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IAuthUser.cs b/Server.Authentication/Models/Interfaces/IAuthUser.cs index ca21109..53ed3b8 100644 --- a/Server.Authentication/Models/Interfaces/IAuthUser.cs +++ b/Server.Authentication/Models/Interfaces/IAuthUser.cs @@ -1,12 +1,12 @@ namespace Server.Authentication.Models.Interfaces; /// -/// Interface for ApplicationUser class +/// Interface for ApplicationUser class. /// public interface IAuthUser { /// - /// User request id from token + /// Gets user request id from token. /// int Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 04292ac..b94688b 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -2,6 +2,7 @@ net6 + true diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 3443a7a..82e1252 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -9,7 +9,7 @@ public sealed class AttemptValidationService private readonly ConcurrentDictionary _failedAttempts = new(); private readonly ConcurrentDictionary _coolDownCollection = new(); - public bool InsertFailAttempt(string userId) + public bool TryInsertFailAttempt(string userId) { if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) { diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 93eed13..24d0e59 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -16,9 +16,6 @@ namespace Server.Authentication.Services; -/// -/// Implements . -/// internal sealed class AuthService : IAuthService { private readonly ServerContext _repository; @@ -92,7 +89,7 @@ public async Task> { _logger.LogWarning("Unable to process account for {Login}", login); - return new UserException(UserExceptionsTemplates.UnknownError,string.Empty); + return new UserException(UserExceptionsTemplates.UnknownError); } finally { @@ -127,7 +124,7 @@ public async Task> LoginAsync(string lo }; } - _attemptValidationService.InsertFailAttempt(login); + _attemptValidationService.TryInsertFailAttempt(login); return new UserException(login.UserInvalidCredentials()); } diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 8b54f1d..e34f727 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -8,15 +8,15 @@ namespace Server.Bll.Services.Interfaces; public interface IRoomService { - Task> CreateRoom(int userId, bool isPrivate = false, bool isTraining = false); + Task> CreateAsync(int userId, bool isPrivate = false, bool isTraining = false); Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); - Task> JoinRoom(int userId, bool isPrivate, string roomCode); + Task> JoinAsync(int userId, bool isPrivate, string roomCode); - Task> GetRoom(int roomId); + Task> GetAsync(int roomId); Task UpdateRoom(RoomModel room); - Task DeleteRoom(int userId, int roomId); + Task DeleteAsync(int userId, int roomId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index d7b64e6..f7cf5b2 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -7,7 +7,7 @@ namespace Server.Bll.Services.Interfaces; public interface IRoundService { - Task> CreateRoundAsync(int userId, int roomId); + Task> CreateAsync(int userId, int roomId); Task MakeMoveAsync(); - Task> UpdateRoundAsync(int userId, RoundModel roundModel); + Task> UpdateAsync(int userId, RoundModel roundModel); } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index c8aec8f..7293f24 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -28,11 +28,12 @@ public RoomService(ServerContext repository, } public async Task> - CreateRoom(int userId, bool isPrivate = false, bool isTraining = false) + CreateAsync(int userId, bool isPrivate = false, bool isTraining = false) { var doesRoomExist = await _repository.RoomPlayersEnumerable .FirstOrDefaultAsync(roomPlayers => roomPlayers.FirstPlayerId == userId || roomPlayers.SecondPlayerId == userId); + if (doesRoomExist is not null) { return new CustomException(ExceptionTemplates.TwinkRoom); @@ -71,7 +72,7 @@ public async Task> return room.Adapt(); } - public async Task> JoinRoom(int userId, bool isPrivate, string roomCode) + public async Task> JoinAsync(int userId, bool isPrivate, string roomCode) { var thisRoom = isPrivate ? await _rooms @@ -116,7 +117,7 @@ public async Task> JoinRoom(int userId, bool i if (thisRoom.RoomPlayers.FirstPlayerId is not 0 && thisRoom.RoomPlayers.SecondPlayerId is not 0) { - await _roundService.CreateRoundAsync(userId, thisRoom.Id); + await _roundService.CreateAsync(userId, thisRoom.Id); } _rooms.Update(thisRoom); @@ -125,7 +126,7 @@ public async Task> JoinRoom(int userId, bool i return thisRoom.Adapt(); } - public async Task> GetRoom(int roomId) + public async Task> GetAsync(int roomId) { var room = await _rooms.FindAsync(roomId); @@ -159,7 +160,7 @@ public async Task> GetRoom(int roomId) return StatusCodes.Status200OK; } - public async Task DeleteRoom(int userId, int roomId) + public async Task DeleteAsync(int userId, int roomId) { var thisRoom = await _rooms .Include(room=>room.RoomPlayers) diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index a1e4788..0cc9c29 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -20,28 +20,38 @@ public RoundService(ServerContext serverContext) { _serverContext = serverContext; } - public async Task> CreateRoundAsync(int userId, int roomId) + public async Task> CreateAsync(int userId, int roomId) { var foundRoom = await _serverContext .Rooms .Include(x => x.RoomPlayers) .FirstOrDefaultAsync(x => x.Id == roomId); + if (foundRoom is null) + { return new CustomException(ExceptionTemplates.RoomNotExists); + } + if (foundRoom.RoomPlayers.FirstPlayerId != userId) { if(foundRoom.RoomPlayers.SecondPlayerId != userId) + { return new CustomException(ExceptionTemplates.NotAllowed); + } } if (foundRoom.RoomPlayers.SecondPlayerId != userId) { if(foundRoom.RoomPlayers.FirstPlayerId != userId) + { return new CustomException(ExceptionTemplates.NotAllowed); + } } if (!foundRoom.IsFull) + { return new CustomException(ExceptionTemplates.RoomNotFull); + } var newRound = new Round { @@ -67,7 +77,7 @@ public Task MakeMoveAsync() throw new NotImplementedException(); } - public async Task> UpdateRoundAsync(int userId, RoundModel roundModel) + public async Task> UpdateAsync(int userId, RoundModel roundModel) { var thisRound = await _serverContext .Rounds @@ -76,9 +86,14 @@ public async Task> UpdateRoundAsync(int userId .FirstOrDefaultAsync(x => x.Id == roundModel.Id); if(thisRound is null) + { return new CustomException(ExceptionTemplates.RoundNotFound(roundModel.Id)); + } + if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) + { return new CustomException(ExceptionTemplates.NotAllowed); + } var incomeRound = roundModel.Adapt(); thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; @@ -92,10 +107,13 @@ public async Task> UpdateRoundAsync(int userId } if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) + { return new CustomException(ExceptionTemplates.NotAllowed); + } _serverContext.Update(thisRound); await _serverContext.SaveChangesAsync(); + return thisRound.Adapt(); } } \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index e981d11..445cdc2 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -28,8 +28,8 @@ public async Task> GetAllStatistics() public async Task GetPersonalStatistics(int userId) { var statistics = await _repository.StatisticsEnumerable - .Include(x=>x.Account) - .FirstOrDefaultAsync(x=>x.Id == userId); + .Include(x=> x.Account) + .FirstOrDefaultAsync(statistics => statistics.Id == userId); return statistics.Adapt(); } diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs index c853950..bb16d5f 100644 --- a/Server.Dal/Entities/RoomPlayers.cs +++ b/Server.Dal/Entities/RoomPlayers.cs @@ -9,14 +9,21 @@ public class RoomPlayers [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + public int PlayersCount { get; set; } + public int RoomId { get; set; } + [ForeignKey("RoomId")] public virtual Room Room { get; set; } + public int? FirstPlayerId { get; set; } + [ForeignKey("FirstPlayerId")] public virtual Account FirstPlayer { get; set; } + public int? SecondPlayerId { get; set; } [ForeignKey("SecondPlayerId")] + public virtual Account SecondPlayer { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 42d499b..80abf78 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -9,19 +9,29 @@ public class Round [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; init; } + public int RoomPlayersId { get; set; } + [ForeignKey("RoomPlayersId")] public virtual RoomPlayers RoomPlayers { get; set; } + public int FirstPlayerMove { get; set; } + public int SecondPlayerMove { get; set; } + public int? WinnerId { get; set; } + [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } + public int? LoserId { get; set; } + [ForeignKey("LoserId")] public virtual Account Loser { get; set; } + public long LastMoveTicks { get; set; } + public long TimeFinishedTicks { get; set; } + public bool IsFinished { get; set; } - } \ No newline at end of file diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index 2f159fa..c999d49 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -70,5 +70,4 @@ public class Statistics /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// public int Score { get; set; } - } \ No newline at end of file diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 810aa12..9d4a3fe 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -34,7 +34,7 @@ public async Task CreateRoom([FromQuery] bool isPrivate, [FromHeader(Name="X-Training")] bool isTraining = false) { var newRoom = await _roomService - .CreateRoom(UserId, isPrivate, isTraining); + .CreateAsync(UserId, isPrivate, isTraining); return newRoom.Match( Ok, @@ -44,7 +44,7 @@ public async Task CreateRoom([FromQuery] bool isPrivate, [HttpPost("join/public")] public async Task JoinPublicRoom() { - var result = await _roomService.JoinRoom(UserId, true,null); + var result = await _roomService.JoinAsync(UserId, true,null); return result.Match( Ok, exception => BadRequest(exception)); @@ -54,7 +54,7 @@ public async Task JoinPublicRoom() [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task JoinPrivateRoom([FromQuery] string roomCode) { - var result = await _roomService.JoinRoom(UserId, false, roomCode); + var result = await _roomService.JoinAsync(UserId, false, roomCode); return result.Match( Ok, exception => BadRequest(exception)); @@ -78,7 +78,7 @@ public async Task UpdateRoom([FromBody] RoomModel roomModel) [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task DeleteRoom([FromQuery] int roomId) { - var deleteResponse = await _roomService.DeleteRoom(UserId,roomId); + var deleteResponse = await _roomService.DeleteAsync(UserId,roomId); return deleteResponse switch { diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 17b55de..2603865 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -39,7 +39,7 @@ public RoundController( [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task CreateRound(int roomId) { - var result = await _roundService.CreateRoundAsync(UserId, roomId); + var result = await _roundService.CreateAsync(UserId, roomId); return result.Match( Ok, exception => BadRequest(exception)); @@ -54,7 +54,7 @@ public async Task CreateRound(int roomId) [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateCurrentRound(RoundModel roundModel) { - var updateResult = await _roundService.UpdateRoundAsync(UserId, roundModel); + var updateResult = await _roundService.UpdateAsync(UserId, roundModel); return updateResult.Match( Ok, exception => BadRequest(exception)); diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index 81e669f..db71087 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -16,14 +17,11 @@ public static class SwaggerExtension /// Service collection. public static IServiceCollection AddSwagger(this IServiceCollection services) { - if (services == null) throw new ArgumentNullException(nameof(services)); - - //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - + if (services is null) throw new ArgumentNullException(nameof(services)); + services.AddSwaggerGen(options => { - //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - //options.IncludeXmlComments(xmlPath); + options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); options.AddSecurityRequirement( From bc5393a7e76471015361ad1fedc367e95fdb7f07 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Fri, 6 May 2022 23:13:17 +0300 Subject: [PATCH 33/56] Update dotnet.yml --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4e951fe..0132e36 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2 with: - dotnet-version: 5.0.201 + dotnet-version: '6.0.x' - name: Restore dependencies run: dotnet restore - name: Build From 4ad7aa4f54b4308b291a45af59392e4f1485f587 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 7 May 2022 11:52:51 +0300 Subject: [PATCH 34/56] Refactored entities --- .../Server.Authentication.csproj | 1 - Server.Authentication/Services/AuthService.cs | 25 +- Server.Bll/Models/ShortStatisticsModel.cs | 11 + .../Services/Interfaces/IStatisticsService.cs | 2 +- Server.Bll/Services/LongPollingService.cs | 4 +- Server.Bll/Services/RoomService.cs | 313 +++++++++--------- Server.Bll/Services/RoundService.cs | 168 +++++----- Server.Bll/Services/StatisticsService.cs | 4 +- Server.Dal/Context/ServerContext.cs | 54 +-- Server.Dal/Entities/Account.cs | 22 +- Server.Dal/Entities/Player.cs | 19 ++ Server.Dal/Entities/Room.cs | 20 +- Server.Dal/Entities/RoomPlayers.cs | 29 -- Server.Dal/Entities/Round.cs | 27 +- Server.Dal/Entities/Statistics.cs | 67 +--- ... 20220507084026_InitialCommit.Designer.cs} | 148 +++------ ...on2.cs => 20220507084026_InitialCommit.cs} | 230 +++++-------- .../20220507084330_FixStatistics.Designer.cs | 239 +++++++++++++ .../20220507084330_FixStatistics.cs | 32 ++ .../Migrations/ServerContextModelSnapshot.cs | 146 +++----- Server.Host/Contracts/StatisticsDto.cs | 6 +- Server.Host/Controllers/AccountController.cs | 39 +-- .../Controllers/LongPollingController.cs | 14 +- Server.Host/Controllers/RoomController.cs | 3 +- Server.Host/Controllers/RoundController.cs | 2 +- .../Controllers/StatisticsController.cs | 16 +- Server.Host/Extensions/SwaggerExtension.cs | 3 +- Server.Host/Properties/launchSettings.json | 2 +- Server.Host/Server.Host.csproj | 5 +- 29 files changed, 859 insertions(+), 792 deletions(-) create mode 100644 Server.Bll/Models/ShortStatisticsModel.cs create mode 100644 Server.Dal/Entities/Player.cs delete mode 100644 Server.Dal/Entities/RoomPlayers.cs rename Server.Dal/Migrations/{20210720112106_NewFluentAssertion2.Designer.cs => 20220507084026_InitialCommit.Designer.cs} (66%) rename Server.Dal/Migrations/{20210720112106_NewFluentAssertion2.cs => 20220507084026_InitialCommit.cs} (55%) create mode 100644 Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs create mode 100644 Server.Dal/Migrations/20220507084330_FixStatistics.cs diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index b94688b..04292ac 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -2,7 +2,6 @@ net6 - true diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 24d0e59..e27fb73 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -66,22 +66,23 @@ public async Task> { Login = login, Password = password.EncodeBase64(), + Statistics = new Statistics() }; - await _accounts.AddAsync(account); + _accounts.Add(account); await _repository.SaveChangesAsync(); - var accountStatistics = new Statistics - { - AccountId = account.Id - }; - - await _repository.StatisticsEnumerable.AddAsync(accountStatistics); - await _repository.SaveChangesAsync(); - - account.StatisticsId = accountStatistics.Id; - - await _repository.SaveChangesAsync(); + // var accountStatistics = new Statistics + // { + // AccountId = account.Id + // }; + // + // await _repository.StatisticsEnumerable.AddAsync(accountStatistics); + // await _repository.SaveChangesAsync(); + // + // account.StatisticsId = accountStatistics.Id; + // + // await _repository.SaveChangesAsync(); return StatusCodes.Status200OK; } diff --git a/Server.Bll/Models/ShortStatisticsModel.cs b/Server.Bll/Models/ShortStatisticsModel.cs new file mode 100644 index 0000000..34218ed --- /dev/null +++ b/Server.Bll/Models/ShortStatisticsModel.cs @@ -0,0 +1,11 @@ +namespace Server.Bll.Models; + +public sealed class ShortStatisticsModel +{ + public AccountModel Account { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + public int? Score { get; set; } +} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs index cfea871..659dfb8 100644 --- a/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ b/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -6,6 +6,6 @@ namespace Server.Bll.Services.Interfaces; public interface IStatisticsService { - Task> GetAllStatistics(); + Task> GetAllStatistics(); Task GetPersonalStatistics(int userId); } \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index 6418136..c2b94be 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -23,7 +23,7 @@ public async Task CheckRoomState(int roomId) public async Task CheckRoundState(int roundId) { var thisRound = await _serverContext.Rounds.FindAsync(roundId.ToString()); - - return thisRound is {WinnerId: null}; + + return thisRound.IsFinished; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 7293f24..c60af14 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -30,100 +30,107 @@ public RoomService(ServerContext repository, public async Task> CreateAsync(int userId, bool isPrivate = false, bool isTraining = false) { - var doesRoomExist = await _repository.RoomPlayersEnumerable - .FirstOrDefaultAsync(roomPlayers => roomPlayers.FirstPlayerId == userId - || roomPlayers.SecondPlayerId == userId); - - if (doesRoomExist is not null) - { - return new CustomException(ExceptionTemplates.TwinkRoom); - } - - var room = new Room - { - IsPrivate = isPrivate, - RoomCode = Guid.NewGuid().ToString("n")[..8], - IsFull = false, - CreationTimeTicks = DateTimeOffset.Now.Ticks - }; - - await _rooms.AddAsync(room); - await _repository.SaveChangesAsync(); - - var newRoomPlayers = new RoomPlayers - { - RoomId = room.Id, - FirstPlayerId = userId, - PlayersCount = 1 - }; - - if (isTraining) - { - var bot = await _repository.Accounts.FirstAsync(account => account.Login.Equals("BOT", StringComparison.OrdinalIgnoreCase)); - newRoomPlayers.SecondPlayerId = bot.Id; - } - - await _repository.RoomPlayersEnumerable.AddAsync(newRoomPlayers); - room.RoomPlayers = newRoomPlayers; - _rooms.Update(room); - - await _repository.SaveChangesAsync(); - - return room.Adapt(); + throw new NotImplementedException(); + // var doesRoomExist = await _repository.RoomPlayersEnumerable + // .AnyAsync(roomPlayers => roomPlayers.FirstPlayerId == userId + // || roomPlayers.SecondPlayerId == userId); + // + // if (doesRoomExist) + // { + // return new CustomException(ExceptionTemplates.TwinkRoom); + // } + // + // var room = new Room + // { + // IsPrivate = isPrivate, + // Code = Guid.NewGuid().ToString("n")[..8], + // IsFull = false, + // CreationTimeTicks = DateTimeOffset.Now.Ticks, + // Player = new Player + // { + // FirstPlayerId = userId, + // Count = 1 + // } + // }; + // + // // await _rooms.AddAsync(room); + // // await _repository.SaveChangesAsync(); + // + // // var newRoomPlayers = new RoomPlayers + // // { + // // RoomId = room.Id, + // // FirstPlayerId = userId, + // // PlayersCount = 1 + // // }; + // // + // if (isTraining) + // { + // room.Player.SecondPlayerId = 0; + // } + // + // _repository.Add(room); + // + // // room.RoomPlayers = newRoomPlayers; + // // _rooms.Update(room); + // // + // await _repository.SaveChangesAsync(); + // + // return room.Adapt(); } public async Task> JoinAsync(int userId, bool isPrivate, string roomCode) { - var thisRoom = isPrivate - ? await _rooms - .Include(room => room.RoomPlayers) - .Where(room => !room.IsFull) - .FirstOrDefaultAsync() - : await _rooms - .Include(room=>room.RoomPlayers). - FirstOrDefaultAsync(room => room.RoomCode == roomCode); - - if (thisRoom is null) - { - return new CustomException(ExceptionTemplates.RoomNotExists); - } - - if (thisRoom.RoomPlayers.FirstPlayerId == userId || thisRoom.RoomPlayers.SecondPlayerId == userId) - { - return new CustomException(ExceptionTemplates.AlreadyInRoom); - } - - if (thisRoom.RoomPlayers.FirstPlayerId is not 0 && thisRoom.RoomPlayers.SecondPlayerId is not 0) - { - return new CustomException(ExceptionTemplates.RoomFull); - } - - if (thisRoom.RoomPlayers.FirstPlayerId is not 0) - { - thisRoom.RoomPlayers.FirstPlayerId = userId; - _rooms.Update(thisRoom); - - await _repository.SaveChangesAsync(); - - return thisRoom.Adapt(); - } - - if (thisRoom.RoomPlayers.SecondPlayerId is 0) - { - return new CustomException(ExceptionTemplates.Unknown); - } - - thisRoom.RoomPlayers.SecondPlayerId = userId; - - if (thisRoom.RoomPlayers.FirstPlayerId is not 0 && thisRoom.RoomPlayers.SecondPlayerId is not 0) - { - await _roundService.CreateAsync(userId, thisRoom.Id); - } - - _rooms.Update(thisRoom); - await _repository.SaveChangesAsync(); - - return thisRoom.Adapt(); + throw new NotImplementedException(); + // var thisRoom = isPrivate + // ? await _rooms + // .Include(room => room.Player) + // .Where(room => !room.IsFull) + // .FirstOrDefaultAsync() + // : await _rooms + // .Include(room=>room.Player). + // FirstOrDefaultAsync(room => room.Code == roomCode); + // + // if (thisRoom is null) + // { + // return new CustomException(ExceptionTemplates.RoomNotExists); + // } + // + // if (thisRoom.Player.FirstPlayerId == userId || thisRoom.Player.SecondPlayerId == userId) + // { + // return new CustomException(ExceptionTemplates.AlreadyInRoom); + // } + // + // if (thisRoom.Player.FirstPlayerId is not 0 && thisRoom.Player.SecondPlayerId is not 0) + // { + // return new CustomException(ExceptionTemplates.RoomFull); + // } + // + // if (thisRoom.Player.FirstPlayerId is not 0) + // { + // thisRoom.Player.FirstPlayerId = userId; + // _rooms.Update(thisRoom); + // + // await _repository.SaveChangesAsync(); + // + // return thisRoom.Adapt(); + // } + // + // if (thisRoom.Player.SecondPlayerId is 0) + // { + // return new CustomException(ExceptionTemplates.Unknown); + // } + // + // thisRoom.Player.SecondPlayerId = userId; + // + // if (thisRoom.Player.FirstPlayerId is not 0 && thisRoom.Player.SecondPlayerId is not 0) + // { + // await _roundService.CreateAsync(userId, thisRoom.Id); + // } + // + // _rooms.Update(thisRoom); + // await _repository.SaveChangesAsync(); + // + // return thisRoom.Adapt(); } public async Task> GetAsync(int roomId) @@ -137,55 +144,57 @@ public async Task> GetAsync(int roomId) public async Task UpdateRoom(RoomModel room) { - var thisRoom = await _rooms.FindAsync(room.Id); - - if (thisRoom is null) - { - return StatusCodes.Status400BadRequest; - } - - var updatedRoom = room.Adapt(); - - thisRoom.IsFull = updatedRoom.IsFull; - thisRoom.IsPrivate = updatedRoom.IsPrivate; - thisRoom.RoundId = updatedRoom.RoundId; - - if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) - { - return StatusCodes.Status400BadRequest; - } - - _repository.Update(thisRoom); - - return StatusCodes.Status200OK; + throw new NotImplementedException(); + // var thisRoom = await _rooms.FindAsync(room.Id); + // + // if (thisRoom is null) + // { + // return StatusCodes.Status400BadRequest; + // } + // + // var updatedRoom = room.Adapt(); + // + // thisRoom.IsFull = updatedRoom.IsFull; + // thisRoom.IsPrivate = updatedRoom.IsPrivate; + // thisRoom.RoundId = updatedRoom.RoundId; + // + // if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) + // { + // return StatusCodes.Status400BadRequest; + // } + // + // _repository.Update(thisRoom); + // + // return StatusCodes.Status200OK; } public async Task DeleteAsync(int userId, int roomId) { - var thisRoom = await _rooms - .Include(room=>room.RoomPlayers) - .FirstOrDefaultAsync(room => room.Id == roomId); - - if (thisRoom is null) - { - return StatusCodes.Status400BadRequest; - } - - if (thisRoom.RoomPlayers.FirstPlayerId != userId) - { - return StatusCodes.Status400BadRequest; - } - - if (thisRoom.RoomPlayers.SecondPlayerId != userId) - { - return StatusCodes.Status400BadRequest; - } - - _rooms.Remove(thisRoom); - - await _repository.SaveChangesAsync(); - - return StatusCodes.Status200OK; + throw new NotImplementedException(); + // var thisRoom = await _rooms + // .Include(room=>room.Player) + // .FirstOrDefaultAsync(room => room.Id == roomId); + // + // if (thisRoom is null) + // { + // return StatusCodes.Status400BadRequest; + // } + // + // if (thisRoom.Player.FirstPlayerId != userId) + // { + // return StatusCodes.Status400BadRequest; + // } + // + // if (thisRoom.Player.SecondPlayerId != userId) + // { + // return StatusCodes.Status400BadRequest; + // } + // + // _rooms.Remove(thisRoom); + // + // await _repository.SaveChangesAsync(); + // + // return StatusCodes.Status200OK; } /// @@ -197,21 +206,23 @@ public async Task> GetAsync(int roomId) [Obsolete(message:"Should be carefully used")] public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) { - var currentDate = DateTimeOffset.Now.Ticks; - - var rooms = await _rooms - .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.RoundId == null) - .ToArrayAsync(); - - var allRound = await _repository.Rounds - .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) - .ToArrayAsync(); - - _rooms.RemoveRange(rooms); - _repository.Rounds.RemoveRange(allRound); - - await _repository.SaveChangesAsync(); - - return rooms.Length + allRound.Length; + return 0; + throw new NotImplementedException(); + // var currentDate = DateTimeOffset.Now.Ticks; + // + // var rooms = await _rooms + // .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.RoundId == null) + // .ToArrayAsync(); + // + // var allRound = await _repository.Rounds + // .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) + // .ToArrayAsync(); + // + // _rooms.RemoveRange(rooms); + // _repository.Rounds.RemoveRange(allRound); + // + // await _repository.SaveChangesAsync(); + // + // return rooms.Length + allRound.Length; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index 0cc9c29..be1a3b2 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -22,53 +22,54 @@ public RoundService(ServerContext serverContext) } public async Task> CreateAsync(int userId, int roomId) { - var foundRoom = await _serverContext - .Rooms - .Include(x => x.RoomPlayers) - .FirstOrDefaultAsync(x => x.Id == roomId); - - if (foundRoom is null) - { - return new CustomException(ExceptionTemplates.RoomNotExists); - } - - if (foundRoom.RoomPlayers.FirstPlayerId != userId) - { - if(foundRoom.RoomPlayers.SecondPlayerId != userId) - { - return new CustomException(ExceptionTemplates.NotAllowed); - } - - } - if (foundRoom.RoomPlayers.SecondPlayerId != userId) - { - if(foundRoom.RoomPlayers.FirstPlayerId != userId) - { - return new CustomException(ExceptionTemplates.NotAllowed); - } - } - - if (!foundRoom.IsFull) - { - return new CustomException(ExceptionTemplates.RoomNotFull); - } - - var newRound = new Round - { - RoomPlayersId = foundRoom.RoomPlayers.Id, - FirstPlayerMove = 0, - SecondPlayerMove = 0, - LastMoveTicks = DateTimeOffset.Now.Ticks, - TimeFinishedTicks = 0, - IsFinished = false - }; - - await _serverContext.AddAsync(newRound); - - foundRoom.RoundId = newRound.Id; - await _serverContext.SaveChangesAsync(); - - return newRound.Adapt(); + throw new NotImplementedException(); + // var foundRoom = await _serverContext + // .Rooms + // .Include(x => x.RoomPlayer) + // .FirstOrDefaultAsync(x => x.Id == roomId); + // + // if (foundRoom is null) + // { + // return new CustomException(ExceptionTemplates.RoomNotExists); + // } + // + // if (foundRoom.RoomPlayer.FirstPlayerId != userId) + // { + // if(foundRoom.RoomPlayer.SecondPlayerId != userId) + // { + // return new CustomException(ExceptionTemplates.NotAllowed); + // } + // + // } + // if (foundRoom.RoomPlayer.SecondPlayerId != userId) + // { + // if(foundRoom.RoomPlayer.FirstPlayerId != userId) + // { + // return new CustomException(ExceptionTemplates.NotAllowed); + // } + // } + // + // if (!foundRoom.IsFull) + // { + // return new CustomException(ExceptionTemplates.RoomNotFull); + // } + // + // var newRound = new Round + // { + // RoomPlayersId = foundRoom.RoomPlayer.Id, + // FirstPlayerMove = 0, + // SecondPlayerMove = 0, + // LastMoveTicks = DateTimeOffset.Now.Ticks, + // TimeFinishedTicks = 0, + // IsFinished = false + // }; + // + // await _serverContext.AddAsync(newRound); + // + // foundRoom.RoundId = newRound.Id; + // await _serverContext.SaveChangesAsync(); + // + // return newRound.Adapt(); } [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] @@ -79,41 +80,42 @@ public Task MakeMoveAsync() public async Task> UpdateAsync(int userId, RoundModel roundModel) { - var thisRound = await _serverContext - .Rounds - .Include(x => x.RoomPlayers) - .ThenInclude(x=>x.Room) - .FirstOrDefaultAsync(x => x.Id == roundModel.Id); - - if(thisRound is null) - { - return new CustomException(ExceptionTemplates.RoundNotFound(roundModel.Id)); - } - - if(thisRound.RoomPlayers.FirstPlayerId != userId || thisRound.RoomPlayers.SecondPlayerId != userId) - { - return new CustomException(ExceptionTemplates.NotAllowed); - } - - var incomeRound = roundModel.Adapt(); - thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; - thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; - thisRound.LastMoveTicks = incomeRound.LastMoveTicks; - - if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) - { - thisRound.IsFinished = true; - thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; - } - - if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) - { - return new CustomException(ExceptionTemplates.NotAllowed); - } - - _serverContext.Update(thisRound); - await _serverContext.SaveChangesAsync(); - - return thisRound.Adapt(); + throw new NotImplementedException(); + // var thisRound = await _serverContext + // .Rounds + // .Include(x => x.Player) + // .ThenInclude(x=>x.Room) + // .FirstOrDefaultAsync(x => x.Id == roundModel.Id); + // + // if(thisRound is null) + // { + // return new CustomException(ExceptionTemplates.RoundNotFound(roundModel.Id)); + // } + // + // if(thisRound.Player.FirstPlayerId != userId || thisRound.Player.SecondPlayerId != userId) + // { + // return new CustomException(ExceptionTemplates.NotAllowed); + // } + // + // var incomeRound = roundModel.Adapt(); + // thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; + // thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; + // thisRound.LastMoveTicks = incomeRound.LastMoveTicks; + // + // if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) + // { + // thisRound.IsFinished = true; + // thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; + // } + // + // if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) + // { + // return new CustomException(ExceptionTemplates.NotAllowed); + // } + // + // _serverContext.Update(thisRound); + // await _serverContext.SaveChangesAsync(); + // + // return thisRound.Adapt(); } } \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index 445cdc2..acce3ff 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -17,11 +17,11 @@ public StatisticsService(ServerContext repository) _repository = repository; } - public async Task> GetAllStatistics() + public async Task> GetAllStatistics() { return await _repository .StatisticsEnumerable - .ProjectToType() + .ProjectToType() .ToArrayAsync(); } diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index 2cb36d7..aef3f24 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -7,7 +7,7 @@ public sealed class ServerContext : DbContext { public DbSet Accounts { get; set; } public DbSet Rooms { get; set; } - public DbSet RoomPlayersEnumerable { get; set; } + public DbSet RoomPlayersEnumerable { get; set; } public DbSet Rounds { get; set; } public DbSet StatisticsEnumerable { get; set; } @@ -20,31 +20,31 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasQueryFilter(x => !x.IsFinished); - modelBuilder.Entity() - .HasOne(invite => invite.FirstPlayer) - .WithMany(user => user.FirstPlayer) - .HasForeignKey(invite => invite.FirstPlayerId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(players => players.Room) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(x => x.RoomPlayers) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(invite => invite.SecondPlayer) - .WithMany(user => user.SecondPlayer) - .HasForeignKey(invite => invite.SecondPlayerId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(x => x.RoomPlayers) - .WithOne() - .OnDelete(DeleteBehavior.NoAction); + // modelBuilder.Entity() + // .HasOne(invite => invite.FirstPlayer) + // .WithMany(user => user.FirstPlayer) + // .HasForeignKey(invite => invite.FirstPlayerId) + // .OnDelete(DeleteBehavior.NoAction); + // + // modelBuilder.Entity() + // .HasOne(players => players.Room) + // .WithOne() + // .OnDelete(DeleteBehavior.Cascade); + // + // modelBuilder.Entity() + // .HasOne(x => x.RoomPlayer) + // .WithOne() + // .OnDelete(DeleteBehavior.Cascade); + // + // modelBuilder.Entity() + // .HasOne(invite => invite.SecondPlayer) + // .WithMany(user => user.SecondPlayer) + // .HasForeignKey(invite => invite.SecondPlayerId) + // .OnDelete(DeleteBehavior.NoAction); + // + // modelBuilder.Entity() + // .HasOne(x => x.RoomPlayer) + // .WithOne() + // .OnDelete(DeleteBehavior.NoAction); } } \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index d27d8d6..da87113 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Dal.Entities; @@ -8,14 +7,14 @@ namespace Server.Dal.Entities; public class Account { /// - /// Id of account. Unique to everyone and similar with Statistics Id + /// Id of account. Unique to everyone and similar with Statistics Id /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + public int Id { get; init; } /// - /// Nick name of Account + /// Nick name of Account. /// public string Login { get; set; } @@ -23,18 +22,9 @@ public class Account /// Password of the Account /// public string Password { get; set; } - + /// - /// Statistics id, connected to this account + /// Linked to this player statistics /// - public int? StatisticsId { get; set; } - - /// - /// Linked to this player statistics - /// - [ForeignKey("StatisticsId")] public virtual Statistics Statistics { get; set; } - - public virtual ICollection FirstPlayer { get; set; } - public virtual ICollection SecondPlayer { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Player.cs b/Server.Dal/Entities/Player.cs new file mode 100644 index 0000000..45f8c2e --- /dev/null +++ b/Server.Dal/Entities/Player.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Server.Dal.Entities; + +[Table("Players")] +public class Player +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; init; } + + [ForeignKey("Account")] + public int AccountId { get; set; } + + public virtual Account Account { get; set; } + + public int Move { get; set; } +} \ No newline at end of file diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index c19c390..4d72db1 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -11,29 +11,19 @@ public class Room /// [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + public int Id { get; init; } /// /// Special code to join a room /// - public string RoomCode { get; set; } - - /// - /// Id of current round - /// - public int? RoundId { get; set; } - + public string Code { get; set; } + /// /// Round, linked to this room /// - [ForeignKey("RoundId")] public virtual Round Round { get; set; } - - public int? RoomPlayerId { get; set; } - - [ForeignKey("RoomPlayerId")] - public virtual RoomPlayers RoomPlayers { get; set; } - + public virtual Player Player { get; set; } + /// /// Flag is this room is private /// diff --git a/Server.Dal/Entities/RoomPlayers.cs b/Server.Dal/Entities/RoomPlayers.cs deleted file mode 100644 index bb16d5f..0000000 --- a/Server.Dal/Entities/RoomPlayers.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Server.Dal.Entities; - -[Table("RoomPlayers")] -public class RoomPlayers -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } - - public int PlayersCount { get; set; } - - public int RoomId { get; set; } - - [ForeignKey("RoomId")] - public virtual Room Room { get; set; } - - public int? FirstPlayerId { get; set; } - - [ForeignKey("FirstPlayerId")] - public virtual Account FirstPlayer { get; set; } - - public int? SecondPlayerId { get; set; } - [ForeignKey("SecondPlayerId")] - - public virtual Account SecondPlayer { get; set; } -} \ No newline at end of file diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 80abf78..826073d 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Dal.Entities; @@ -9,29 +10,17 @@ public class Round [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; init; } + + [ForeignKey("Room")] + public int RoomId { get; set; } - public int RoomPlayersId { get; set; } - - [ForeignKey("RoomPlayersId")] - public virtual RoomPlayers RoomPlayers { get; set; } - - public int FirstPlayerMove { get; set; } - - public int SecondPlayerMove { get; set; } + public virtual Room Room { get; set; } - public int? WinnerId { get; set; } + public virtual ICollection Players { get; set; } - [ForeignKey("WinnerId")] public virtual Account Winner { get; set; } - public int? LoserId { get; set; } - - [ForeignKey("LoserId")] public virtual Account Loser { get; set; } - - public long LastMoveTicks { get; set; } - - public long TimeFinishedTicks { get; set; } - + public bool IsFinished { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index c999d49..3171af1 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Dal.Entities; @@ -6,68 +7,30 @@ namespace Server.Dal.Entities; [Table("Statistics")] public class Statistics { - /// - /// Id of statistics. Equivalent to Account Id - /// - [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } - - /// - /// Id of linked account - /// + + [ForeignKey("Account")] public int AccountId { get; set; } - - /// - /// Linked account - /// - [ForeignKey("AccountId")] + public virtual Account Account { get; set; } - - /// - /// Total amount of Wins - /// + public int Wins { get; set; } - - /// - /// Total amount of Loses - /// + public int Loss { get; set; } - - /// - /// Total amount of Draws. OBSOLETE - /// - + public int Draws { get; set; } - - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// + public double WinLossRatio { get; set; } - - /// - /// Ratio for the last 7 days - /// - public string TimeSpent { get; set; } - - /// - /// Times used rock - /// + + public TimeSpan TimeSpent { get; set; } + public int UsedRock { get; set; } - - /// - /// Times used Paper - /// + public int UsedPaper { get; set; } - - /// - /// Times used Scissors - /// + public int UsedScissors { get; set; } - - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// + public int Score { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs b/Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs similarity index 66% rename from Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs rename to Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs index 507e14c..f58fc02 100644 --- a/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.Designer.cs +++ b/Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs @@ -9,8 +9,8 @@ namespace Server.Dal.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20210720112106_NewFluentAssertion2")] - partial class NewFluentAssertion2 + [Migration("20220507084026_InitialCommit")] + partial class InitialCommit { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -30,35 +30,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - b.HasKey("Id"); - b.HasIndex("StatisticsId"); - b.ToTable("Accounts"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Dal.Entities.Player", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") + b.Property("AccountId") .HasColumnType("INTEGER"); - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") + b.Property("Move") .HasColumnType("INTEGER"); b.Property("RoundId") @@ -66,42 +52,39 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("RoomPlayerId") - .IsUnique(); + b.HasIndex("AccountId"); b.HasIndex("RoundId"); - b.ToTable("Rooms"); + b.ToTable("Players"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Room", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("FirstPlayerId") + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") .HasColumnType("INTEGER"); - b.Property("PlayersCount") + b.Property("IsFull") .HasColumnType("INTEGER"); - b.Property("RoomId") + b.Property("IsPrivate") .HasColumnType("INTEGER"); - b.Property("SecondPlayerId") + b.Property("PlayerId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("SecondPlayerId"); + b.HasIndex("PlayerId"); - b.ToTable("RoomPlayers"); + b.ToTable("Rooms"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => @@ -110,25 +93,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LastMoveTicks") - .HasColumnType("INTEGER"); - b.Property("LoserId") .HasColumnType("INTEGER"); - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") + b.Property("RoomId") .HasColumnType("INTEGER"); b.Property("WinnerId") @@ -138,7 +109,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("LoserId"); - b.HasIndex("RoomPlayersId") + b.HasIndex("RoomId") .IsUnique(); b.HasIndex("WinnerId"); @@ -184,59 +155,34 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("AccountId"); + b.HasIndex("AccountId") + .IsUnique(); b.ToTable("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Dal.Entities.Player", b => { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + b.HasOne("Server.Dal.Entities.Account", "Account") .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() + b.HasOne("Server.Dal.Entities.Round", null) + .WithMany("Players") .HasForeignKey("RoundId"); - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); + b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Room", b => { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); + b.HasOne("Server.Dal.Entities.Player", "Player") + .WithMany() + .HasForeignKey("PlayerId"); - b.Navigation("SecondPlayer"); + b.Navigation("Player"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => @@ -245,10 +191,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .WithMany() .HasForeignKey("LoserId"); - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") - .OnDelete(DeleteBehavior.NoAction) + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Dal.Entities.Round", "RoomId") + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Server.Dal.Entities.Account", "Winner") @@ -257,7 +203,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Loser"); - b.Navigation("RoomPlayers"); + b.Navigation("Room"); b.Navigation("Winner"); }); @@ -265,8 +211,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Statistics", b => { b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") + .WithOne("Statistics") + .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -275,9 +221,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Navigation("FirstPlayer"); + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Navigation("Round"); + }); - b.Navigation("SecondPlayer"); + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Navigation("Players"); }); #pragma warning restore 612, 618 } diff --git a/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs b/Server.Dal/Migrations/20220507084026_InitialCommit.cs similarity index 55% rename from Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs rename to Server.Dal/Migrations/20220507084026_InitialCommit.cs index 3390faa..28006cd 100644 --- a/Server.Dal/Migrations/20210720112106_NewFluentAssertion2.cs +++ b/Server.Dal/Migrations/20220507084026_InitialCommit.cs @@ -2,155 +2,144 @@ namespace Server.Dal.Migrations { - public partial class NewFluentAssertion2 : Migration + public partial class InitialCommit : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "RoomPlayers", + name: "Accounts", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - PlayersCount = table.Column(type: "INTEGER", nullable: false), - RoomId = table.Column(type: "INTEGER", nullable: false), - FirstPlayerId = table.Column(type: "INTEGER", nullable: true), - SecondPlayerId = table.Column(type: "INTEGER", nullable: true) + Login = table.Column(type: "TEXT", nullable: true), + Password = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_RoomPlayers", x => x.Id); + table.PrimaryKey("PK_Accounts", x => x.Id); }); migrationBuilder.CreateTable( - name: "Rounds", + name: "Statistics", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - RoomPlayersId = table.Column(type: "INTEGER", nullable: false), - FirstPlayerMove = table.Column(type: "INTEGER", nullable: false), - SecondPlayerMove = table.Column(type: "INTEGER", nullable: false), - WinnerId = table.Column(type: "INTEGER", nullable: true), - LoserId = table.Column(type: "INTEGER", nullable: true), - LastMoveTicks = table.Column(type: "INTEGER", nullable: false), - TimeFinishedTicks = table.Column(type: "INTEGER", nullable: false), - IsFinished = table.Column(type: "INTEGER", nullable: false) + AccountId = table.Column(type: "INTEGER", nullable: false), + Wins = table.Column(type: "INTEGER", nullable: false), + Loss = table.Column(type: "INTEGER", nullable: false), + Draws = table.Column(type: "INTEGER", nullable: false), + WinLossRatio = table.Column(type: "REAL", nullable: false), + TimeSpent = table.Column(type: "TEXT", nullable: true), + UsedRock = table.Column(type: "INTEGER", nullable: false), + UsedPaper = table.Column(type: "INTEGER", nullable: false), + UsedScissors = table.Column(type: "INTEGER", nullable: false), + Score = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Rounds", x => x.Id); + table.PrimaryKey("PK_Statistics", x => x.Id); table.ForeignKey( - name: "FK_Rounds_RoomPlayers_RoomPlayersId", - column: x => x.RoomPlayersId, - principalTable: "RoomPlayers", - principalColumn: "Id"); + name: "FK_Statistics_Accounts_AccountId", + column: x => x.AccountId, + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Rooms", + name: "Players", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - RoomCode = table.Column(type: "TEXT", nullable: true), - RoundId = table.Column(type: "INTEGER", nullable: true), - RoomPlayerId = table.Column(type: "INTEGER", nullable: true), - IsPrivate = table.Column(type: "INTEGER", nullable: false), - IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) + AccountId = table.Column(type: "INTEGER", nullable: false), + Move = table.Column(type: "INTEGER", nullable: false), + RoundId = table.Column(type: "INTEGER", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Rooms", x => x.Id); + table.PrimaryKey("PK_Players", x => x.Id); table.ForeignKey( - name: "FK_Rooms_RoomPlayers_RoomPlayerId", - column: x => x.RoomPlayerId, - principalTable: "RoomPlayers", + name: "FK_Players_Accounts_AccountId", + column: x => x.AccountId, + principalTable: "Accounts", principalColumn: "Id", onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Rooms_Rounds_RoundId", - column: x => x.RoundId, - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Statistics", + name: "Rooms", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - AccountId = table.Column(type: "INTEGER", nullable: false), - Wins = table.Column(type: "INTEGER", nullable: false), - Loss = table.Column(type: "INTEGER", nullable: false), - Draws = table.Column(type: "INTEGER", nullable: false), - WinLossRatio = table.Column(type: "REAL", nullable: false), - TimeSpent = table.Column(type: "TEXT", nullable: true), - UsedRock = table.Column(type: "INTEGER", nullable: false), - UsedPaper = table.Column(type: "INTEGER", nullable: false), - UsedScissors = table.Column(type: "INTEGER", nullable: false), - Score = table.Column(type: "INTEGER", nullable: false) + Code = table.Column(type: "TEXT", nullable: true), + PlayerId = table.Column(type: "INTEGER", nullable: true), + IsPrivate = table.Column(type: "INTEGER", nullable: false), + IsFull = table.Column(type: "INTEGER", nullable: false), + CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Statistics", x => x.Id); + table.PrimaryKey("PK_Rooms", x => x.Id); + table.ForeignKey( + name: "FK_Rooms_Players_PlayerId", + column: x => x.PlayerId, + principalTable: "Players", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Accounts", + name: "Rounds", columns: table => new { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Login = table.Column(type: "TEXT", nullable: true), - Password = table.Column(type: "TEXT", nullable: true), - StatisticsId = table.Column(type: "INTEGER", nullable: true) + RoomId = table.Column(type: "INTEGER", nullable: false), + WinnerId = table.Column(type: "INTEGER", nullable: true), + LoserId = table.Column(type: "INTEGER", nullable: true), + IsFinished = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Accounts", x => x.Id); + table.PrimaryKey("PK_Rounds", x => x.Id); + table.ForeignKey( + name: "FK_Rounds_Accounts_LoserId", + column: x => x.LoserId, + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - column: x => x.StatisticsId, - principalTable: "Statistics", + name: "FK_Rounds_Accounts_WinnerId", + column: x => x.WinnerId, + principalTable: "Accounts", principalColumn: "Id", onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Rounds_Rooms_RoomId", + column: x => x.RoomId, + principalTable: "Rooms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( - name: "IX_Accounts_StatisticsId", - table: "Accounts", - column: "StatisticsId"); - - migrationBuilder.CreateIndex( - name: "IX_RoomPlayers_FirstPlayerId", - table: "RoomPlayers", - column: "FirstPlayerId"); - - migrationBuilder.CreateIndex( - name: "IX_RoomPlayers_RoomId", - table: "RoomPlayers", - column: "RoomId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_RoomPlayers_SecondPlayerId", - table: "RoomPlayers", - column: "SecondPlayerId"); + name: "IX_Players_AccountId", + table: "Players", + column: "AccountId"); migrationBuilder.CreateIndex( - name: "IX_Rooms_RoomPlayerId", - table: "Rooms", - column: "RoomPlayerId", - unique: true); + name: "IX_Players_RoundId", + table: "Players", + column: "RoundId"); migrationBuilder.CreateIndex( - name: "IX_Rooms_RoundId", + name: "IX_Rooms_PlayerId", table: "Rooms", - column: "RoundId"); + column: "PlayerId"); migrationBuilder.CreateIndex( name: "IX_Rounds_LoserId", @@ -158,9 +147,9 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "LoserId"); migrationBuilder.CreateIndex( - name: "IX_Rounds_RoomPlayersId", + name: "IX_Rounds_RoomId", table: "Rounds", - column: "RoomPlayersId", + column: "RoomId", unique: true); migrationBuilder.CreateIndex( @@ -171,68 +160,23 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.CreateIndex( name: "IX_Statistics_AccountId", table: "Statistics", - column: "AccountId"); - - migrationBuilder.AddForeignKey( - name: "FK_RoomPlayers_Accounts_FirstPlayerId", - table: "RoomPlayers", - column: "FirstPlayerId", - principalTable: "Accounts", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_RoomPlayers_Accounts_SecondPlayerId", - table: "RoomPlayers", - column: "SecondPlayerId", - principalTable: "Accounts", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_RoomPlayers_Rooms_RoomId", - table: "RoomPlayers", - column: "RoomId", - principalTable: "Rooms", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds", - column: "LoserId", - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + column: "AccountId", + unique: true); migrationBuilder.AddForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds", - column: "WinnerId", - principalTable: "Accounts", + name: "FK_Players_Rounds_RoundId", + table: "Players", + column: "RoundId", + principalTable: "Rounds", principalColumn: "Id", onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Statistics_Accounts_AccountId", - table: "Statistics", - column: "AccountId", - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropForeignKey( - name: "FK_Accounts_Statistics_StatisticsId", - table: "Accounts"); - - migrationBuilder.DropForeignKey( - name: "FK_RoomPlayers_Accounts_FirstPlayerId", - table: "RoomPlayers"); - - migrationBuilder.DropForeignKey( - name: "FK_RoomPlayers_Accounts_SecondPlayerId", - table: "RoomPlayers"); + name: "FK_Players_Accounts_AccountId", + table: "Players"); migrationBuilder.DropForeignKey( name: "FK_Rounds_Accounts_LoserId", @@ -243,8 +187,8 @@ protected override void Down(MigrationBuilder migrationBuilder) table: "Rounds"); migrationBuilder.DropForeignKey( - name: "FK_RoomPlayers_Rooms_RoomId", - table: "RoomPlayers"); + name: "FK_Players_Rounds_RoundId", + table: "Players"); migrationBuilder.DropTable( name: "Statistics"); @@ -253,13 +197,13 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Accounts"); migrationBuilder.DropTable( - name: "Rooms"); + name: "Rounds"); migrationBuilder.DropTable( - name: "Rounds"); + name: "Rooms"); migrationBuilder.DropTable( - name: "RoomPlayers"); + name: "Players"); } } } diff --git a/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs b/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs new file mode 100644 index 0000000..5dc42ba --- /dev/null +++ b/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs @@ -0,0 +1,239 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220507084330_FixStatistics")] + partial class FixStatistics + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.7"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.Player", "Player") + .WithMany() + .HasForeignKey("PlayerId"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Dal.Entities.Round", "RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20220507084330_FixStatistics.cs b/Server.Dal/Migrations/20220507084330_FixStatistics.cs new file mode 100644 index 0000000..53fc054 --- /dev/null +++ b/Server.Dal/Migrations/20220507084330_FixStatistics.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Server.Dal.Migrations +{ + public partial class FixStatistics : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "TimeSpent", + table: "Statistics", + type: "TEXT", + nullable: false, + defaultValue: new TimeSpan(0, 0, 0, 0, 0), + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "TimeSpent", + table: "Statistics", + type: "TEXT", + nullable: true, + oldClrType: typeof(TimeSpan), + oldType: "TEXT"); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs index 3c9da7f..9032835 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -28,35 +28,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Password") .HasColumnType("TEXT"); - b.Property("StatisticsId") - .HasColumnType("INTEGER"); - b.HasKey("Id"); - b.HasIndex("StatisticsId"); - b.ToTable("Accounts"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Dal.Entities.Player", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") + b.Property("AccountId") .HasColumnType("INTEGER"); - b.Property("RoomCode") - .HasColumnType("TEXT"); - - b.Property("RoomPlayerId") + b.Property("Move") .HasColumnType("INTEGER"); b.Property("RoundId") @@ -64,42 +50,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("RoomPlayerId") - .IsUnique(); + b.HasIndex("AccountId"); b.HasIndex("RoundId"); - b.ToTable("Rooms"); + b.ToTable("Players"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Room", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("FirstPlayerId") + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") .HasColumnType("INTEGER"); - b.Property("PlayersCount") + b.Property("IsFull") .HasColumnType("INTEGER"); - b.Property("RoomId") + b.Property("IsPrivate") .HasColumnType("INTEGER"); - b.Property("SecondPlayerId") + b.Property("PlayerId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("FirstPlayerId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("SecondPlayerId"); + b.HasIndex("PlayerId"); - b.ToTable("RoomPlayers"); + b.ToTable("Rooms"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => @@ -108,25 +91,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("FirstPlayerMove") - .HasColumnType("INTEGER"); - b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LastMoveTicks") - .HasColumnType("INTEGER"); - b.Property("LoserId") .HasColumnType("INTEGER"); - b.Property("RoomPlayersId") - .HasColumnType("INTEGER"); - - b.Property("SecondPlayerMove") - .HasColumnType("INTEGER"); - - b.Property("TimeFinishedTicks") + b.Property("RoomId") .HasColumnType("INTEGER"); b.Property("WinnerId") @@ -136,7 +107,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LoserId"); - b.HasIndex("RoomPlayersId") + b.HasIndex("RoomId") .IsUnique(); b.HasIndex("WinnerId"); @@ -162,7 +133,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Score") .HasColumnType("INTEGER"); - b.Property("TimeSpent") + b.Property("TimeSpent") .HasColumnType("TEXT"); b.Property("UsedPaper") @@ -182,59 +153,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("AccountId"); + b.HasIndex("AccountId") + .IsUnique(); b.ToTable("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Dal.Entities.Player", b => { - b.HasOne("Server.Dal.Entities.Statistics", "Statistics") + b.HasOne("Server.Dal.Entities.Account", "Account") .WithMany() - .HasForeignKey("StatisticsId"); - - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Room", "RoomPlayerId") - .OnDelete(DeleteBehavior.Cascade); + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Server.Dal.Entities.Round", "Round") - .WithMany() + b.HasOne("Server.Dal.Entities.Round", null) + .WithMany("Players") .HasForeignKey("RoundId"); - b.Navigation("RoomPlayers"); - - b.Navigation("Round"); + b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.RoomPlayers", b => + modelBuilder.Entity("Server.Dal.Entities.Room", b => { - b.HasOne("Server.Dal.Entities.Account", "FirstPlayer") - .WithMany("FirstPlayer") - .HasForeignKey("FirstPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne() - .HasForeignKey("Server.Dal.Entities.RoomPlayers", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "SecondPlayer") - .WithMany("SecondPlayer") - .HasForeignKey("SecondPlayerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("FirstPlayer"); - - b.Navigation("Room"); + b.HasOne("Server.Dal.Entities.Player", "Player") + .WithMany() + .HasForeignKey("PlayerId"); - b.Navigation("SecondPlayer"); + b.Navigation("Player"); }); modelBuilder.Entity("Server.Dal.Entities.Round", b => @@ -243,10 +189,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .WithMany() .HasForeignKey("LoserId"); - b.HasOne("Server.Dal.Entities.RoomPlayers", "RoomPlayers") - .WithOne() - .HasForeignKey("Server.Dal.Entities.Round", "RoomPlayersId") - .OnDelete(DeleteBehavior.NoAction) + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Dal.Entities.Round", "RoomId") + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Server.Dal.Entities.Account", "Winner") @@ -255,7 +201,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Loser"); - b.Navigation("RoomPlayers"); + b.Navigation("Room"); b.Navigation("Winner"); }); @@ -263,8 +209,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Statistics", b => { b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") + .WithOne("Statistics") + .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -273,9 +219,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Dal.Entities.Account", b => { - b.Navigation("FirstPlayer"); + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Navigation("Round"); + }); - b.Navigation("SecondPlayer"); + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Navigation("Players"); }); #pragma warning restore 612, 618 } diff --git a/Server.Host/Contracts/StatisticsDto.cs b/Server.Host/Contracts/StatisticsDto.cs index 80e9177..c5157b2 100644 --- a/Server.Host/Contracts/StatisticsDto.cs +++ b/Server.Host/Contracts/StatisticsDto.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Server.Host.Contracts; public sealed class StatisticsDto { - [JsonProperty("Login")] + [JsonPropertyName("Login")] public string Login { get; set; } - [JsonProperty("Score")] + [JsonPropertyName("Score")] public int Score { get; set; } } \ No newline at end of file diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index 8b13c64..d6c5d5b 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -1,8 +1,10 @@ -using System.Net; +using System; +using System.Net; using System.Net.Mime; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Server.Authentication.Exceptions; using Server.Authentication.Services; using Server.Host.Contracts; using Server.Host.Contracts.Requests; @@ -10,7 +12,7 @@ namespace Server.Host.Controllers; [ApiController] -[Route ("api/v1/account/")] +[Route ("api/[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] public sealed class AccountController : ControllerBase @@ -19,47 +21,40 @@ public sealed class AccountController : ControllerBase public AccountController(IAuthService authService) { - _authService = authService; + _authService = authService ?? throw new ArgumentNullException(nameof(authService)); } [HttpPost("register")] - [AllowAnonymous] - [ProducesResponseType(typeof(string), (int)HttpStatusCode.Created)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task Register(RegisterRequest registerRequest) + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] + public async Task RegisterAsync(RegisterRequest registerRequest) { - if (!ModelState.IsValid) - { - return BadRequest(); - } - var newAccount = await _authService .RegisterAsync(registerRequest.Login,registerRequest.Password); return newAccount.Match( - _ => Ok(), - exception => BadRequest(exception)); + statusCode => Ok(statusCode), + userException => BadRequest(userException)); } [HttpPost("login")] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string),(int)HttpStatusCode.BadRequest)] - public async Task Login(AccountDto accountDto) + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] + public async Task LoginAsync(AccountDto accountDto) { - if (!ModelState.IsValid) return BadRequest(); var newAccount = await _authService.LoginAsync(accountDto.Login, accountDto.Password); return newAccount.Match( - Ok, + statusCode => Ok(statusCode), userException => BadRequest(userException)); } [HttpGet("logout")] [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] - public ActionResult Logout(string sessionId) + public ActionResult Logout(string sessionId) { - return Ok(sessionId); + return sessionId; } } \ No newline at end of file diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index ba8f876..4ae2f15 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -1,14 +1,14 @@ +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Server.Bll.Services; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; [ApiController] -[Route("api/v1")] +[Route ("api")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class LongPollingController : ControllerBase { @@ -16,18 +16,18 @@ public sealed class LongPollingController : ControllerBase public LongPollingController(ILongPollingService longPollingService) { - _longPollingService = longPollingService; + _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } [HttpGet("room")] - public async Task CheckRoomState([FromQuery] int roomId) + public Task CheckRoomState([FromQuery] int roomId) { - return await _longPollingService.CheckRoomState(roomId); + return _longPollingService.CheckRoomState(roomId); } [HttpGet("round")] - public async Task CheckRoundState(int roundId) + public Task CheckRoundState(int roundId) { - return await _longPollingService.CheckRoundState(roundId); + return _longPollingService.CheckRoundState(roundId); } } \ No newline at end of file diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 9d4a3fe..18e182e 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -11,7 +11,8 @@ namespace Server.Host.Controllers; [ApiController] -[Route("api/v1/room/")] +[Route ("api/[controller]")] +[Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoomController : ControllerBase diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 2603865..3cff1ad 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -14,7 +14,7 @@ namespace Server.Host.Controllers; /// API Round Controller /// [ApiController] -[Route("api/v1/round")] +[Route ("api/[controller]")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoundController:ControllerBase diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 0a7930c..3fdc3ba 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Net; +using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; @@ -12,7 +14,9 @@ namespace Server.Host.Controllers; [ApiController] -[Route("api/v1/stats")] +[Route ("api/[controller]")] +[Consumes(MediaTypeNames.Application.Json)] +[Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class StatisticsController : ControllerBase { @@ -21,17 +25,17 @@ public sealed class StatisticsController : ControllerBase private int UserId => _applicationUser.Id; public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) { - _statisticsService = statisticsService; - _applicationUser = applicationUser; + _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); + _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } [HttpGet("all")] [AllowAnonymous] [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task> GetOverallStatistics() + public Task> GetOverallStatistics() { - return await _statisticsService.GetAllStatistics(); + return _statisticsService.GetAllStatistics(); } [HttpGet("personal")] diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index db71087..c2b5c6f 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -21,7 +20,7 @@ public static IServiceCollection AddSwagger(this IServiceCollection services) services.AddSwaggerGen(options => { - options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); + // options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.XML"); options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); options.AddSecurityRequirement( diff --git a/Server.Host/Properties/launchSettings.json b/Server.Host/Properties/launchSettings.json index 77b4820..4fe8a9e 100644 --- a/Server.Host/Properties/launchSettings.json +++ b/Server.Host/Properties/launchSettings.json @@ -4,7 +4,7 @@ "Server.Host": { "commandName": "Project", "dotnetRunMessages": "true", - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "applicationUrl": "http://localhost:5000", "environmentVariables": { diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 8b0397a..15bf859 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -1,7 +1,8 @@ - + net6 + enable @@ -10,12 +11,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - From 45655b5c8a08006d69646feeee08f25519ea012e Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 7 May 2022 21:56:23 +0300 Subject: [PATCH 35/56] More fixes, simplified and fixed Entities. Fixed and improves Server.Authentication.csproj. Updated packets --- Server.Authentication/AuthOptions.cs | 9 +- .../Exceptions/UserException.cs | 2 +- .../Extensions/AuthenticationExtension.cs | 17 +- .../Models/ApplicationUser.cs | 8 +- .../Models/Interfaces/IApplicationUser.cs | 2 +- Server.Authentication/Models/Roles.cs | 1 + .../Server.Authentication.csproj | 4 +- .../Services/AttemptValidationService.cs | 26 +- Server.Authentication/Services/AuthService.cs | 95 ++++--- .../Services/IAuthService.cs | 1 + Server.Bll/Exceptions/CustomException.cs | 8 +- Server.Bll/Exceptions/ExceptionTemplates.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../HostedServices/CleanerHostedService.cs | 9 +- Server.Bll/Models/StatisticsModel.cs | 3 + Server.Bll/Server.Bll.csproj | 4 +- .../Services/Interfaces/IStatisticsService.cs | 7 +- Server.Bll/Services/StatisticsService.cs | 22 +- Server.Dal/Context/ServerContext.cs | 31 +-- Server.Dal/Entities/Account.cs | 4 +- Server.Dal/Entities/Player.cs | 6 +- Server.Dal/Entities/Room.cs | 4 +- Server.Dal/Entities/Round.cs | 6 +- Server.Dal/Entities/Statistics.cs | 6 +- Server.Dal/Extensions/SeedingExtension.cs | 24 +- .../20220507084026_InitialCommit.Designer.cs | 239 ------------------ .../20220507084026_InitialCommit.cs | 209 --------------- .../20220507084330_FixStatistics.Designer.cs | 239 ------------------ .../20220507084330_FixStatistics.cs | 32 --- .../Migrations/ServerContextModelSnapshot.cs | 237 ----------------- Server.Dal/Server.Dal.csproj | 10 +- Server.Host/Controllers/RoomController.cs | 73 +++--- Server.Host/Controllers/RoundController.cs | 23 +- .../Controllers/StatisticsController.cs | 23 +- Server.Host/Server.Host.csproj | 10 +- 35 files changed, 221 insertions(+), 1176 deletions(-) delete mode 100644 Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs delete mode 100644 Server.Dal/Migrations/20220507084026_InitialCommit.cs delete mode 100644 Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs delete mode 100644 Server.Dal/Migrations/20220507084330_FixStatistics.cs delete mode 100644 Server.Dal/Migrations/ServerContextModelSnapshot.cs diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index a2e4a1c..5c9fa4f 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -1,9 +1,6 @@ using System; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; -using Server.Authentication.Models; namespace Server.Authentication; @@ -15,12 +12,12 @@ public sealed class AuthOptions /// /// Token issuer (producer). /// - public string Issuer { get; set; } = "RPC"; + public string Issuer { get; set; } = "Rock Paper Scissors"; /// /// Token audience (consumer). /// - public string Audience { get; set; } = "Server.Host"; + public string Audience { get; set; } = "Player"; /// /// Token secret part. @@ -30,7 +27,7 @@ public sealed class AuthOptions /// /// Token life time. /// - public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3d); + public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3); /// /// Require HTTPS. diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server.Authentication/Exceptions/UserException.cs index 8918bba..fa3c546 100644 --- a/Server.Authentication/Exceptions/UserException.cs +++ b/Server.Authentication/Exceptions/UserException.cs @@ -5,7 +5,7 @@ namespace Server.Authentication.Exceptions; /// /// User-related custom structure exception. /// -public readonly struct UserException +public sealed class UserException { /// /// Gets response code. diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index 24a32f2..80cbbce 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -23,15 +23,15 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv _ = services ?? throw new ArgumentNullException(nameof(services)); services.AddOptions(); - - var jwtOptions = services - .BuildServiceProvider() - .GetRequiredService>() - .Value; services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => - { + { + var jwtOptions = services + .BuildServiceProvider() + .GetRequiredService>() + .Value; + options.RequireHttpsMetadata = jwtOptions.RequireHttps; options.TokenValidationParameters = new TokenValidationParameters { @@ -50,9 +50,8 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv services .AddTransient() - .AddTransient() - .AddSingleton(typeof(AttemptValidationService)); - + .AddTransient(); + return services; } } \ No newline at end of file diff --git a/Server.Authentication/Models/ApplicationUser.cs b/Server.Authentication/Models/ApplicationUser.cs index 5ba6591..983eb9c 100644 --- a/Server.Authentication/Models/ApplicationUser.cs +++ b/Server.Authentication/Models/ApplicationUser.cs @@ -24,13 +24,13 @@ public ApplicationUser(IHttpContextAccessor httpContextAccessor) /// /// This user id /// - public int Id => GetUserId(); + public string Id => GetUserId(); - private int GetUserId() + private string GetUserId() { var request = _httpContextAccessor.HttpContext - ?.User.Claims.FirstOrDefault(x => x.Type == "id"); + ?.User.Identity.Name; - return int.TryParse(request?.Value, out var id) ? id : 0; + return request; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs index d4b5742..f34ce2d 100644 --- a/Server.Authentication/Models/Interfaces/IApplicationUser.cs +++ b/Server.Authentication/Models/Interfaces/IApplicationUser.cs @@ -8,5 +8,5 @@ public interface IApplicationUser /// /// Gets user request id from token /// - int Id { get; } + string Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Roles.cs b/Server.Authentication/Models/Roles.cs index 0a3b6b6..7746b64 100644 --- a/Server.Authentication/Models/Roles.cs +++ b/Server.Authentication/Models/Roles.cs @@ -9,6 +9,7 @@ public static class Roles /// Admin role /// public const string Admin = "admin"; + /// /// User role /// diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 04292ac..81ab7be 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 82e1252..6409596 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -3,40 +3,40 @@ namespace Server.Authentication.Services; -public sealed class AttemptValidationService +internal static class AttemptValidationService { // key - userId. Value - failed attempts - private readonly ConcurrentDictionary _failedAttempts = new(); - private readonly ConcurrentDictionary _coolDownCollection = new(); + private static readonly ConcurrentDictionary FailedAttempts = new(); + private static readonly ConcurrentDictionary CoolDownCollection = new(); - public bool TryInsertFailAttempt(string userId) + public static bool TryInsertFailAttempt(this string userId) { - if (_failedAttempts.TryGetValue(userId, out var failedAttempts)) + if (FailedAttempts.TryGetValue(userId, out var failedAttempts)) { if (failedAttempts >= 2) { // todo: in options - _coolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(1)); - _failedAttempts.TryRemove(userId, out _); + CoolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(2)); + FailedAttempts.TryRemove(userId, out _); return true; } } - _failedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); + FailedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); return true; } - public int? CountFailedAttempts(string userId) + public static int? CountFailedAttempts(this string userId) { - return _failedAttempts.TryGetValue(userId, out var failedAttempts) + return FailedAttempts.TryGetValue(userId, out var failedAttempts) ? failedAttempts : default; } - public bool IsCoolDown(string userId, out DateTimeOffset coolDownDate) + public static bool IsCoolDown(this string userId, out DateTimeOffset coolDownDate) { - var result = _coolDownCollection.TryGetValue(userId, out coolDownDate); + var result = CoolDownCollection.TryGetValue(userId, out coolDownDate); if (!result) { @@ -46,7 +46,7 @@ public bool IsCoolDown(string userId, out DateTimeOffset coolDownDate) { return true; } - _coolDownCollection.TryRemove(userId, out coolDownDate); + CoolDownCollection.TryRemove(userId, out coolDownDate); return false; } diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index e27fb73..188efbd 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -18,25 +19,25 @@ namespace Server.Authentication.Services; internal sealed class AuthService : IAuthService { + private static readonly JwtSecurityTokenHandler TokenHandler = new(); + private static SigningCredentials _signingCredentials; + private readonly ServerContext _repository; - private readonly AttemptValidationService _attemptValidationService; private readonly AuthOptions _authOptions; private readonly ILogger _logger; - + private readonly SemaphoreSlim _semaphore = new(1, 1); - private readonly DbSet _accounts; - + public AuthService( ILogger logger, - IOptions authOptions, - AttemptValidationService attemptValidationService, + IOptions authOptions, ServerContext repository) { _logger = logger; - _attemptValidationService = attemptValidationService; _repository = repository; _authOptions = authOptions.Value; - _accounts = repository.Accounts; + + _signingCredentials = new SigningCredentials(_authOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256); } /// @@ -53,37 +54,35 @@ public async Task> return new UserException(nameof(password).UserInvalidCredentials()); } - var release = await _semaphore.WaitAsync(1000); + var release = await _semaphore.WaitAsync(100); try { - if (await _accounts.AnyAsync(account => account.Login.Equals(login))) + if (await _repository.Accounts.AnyAsync(account => account.Login.Equals(login.ToLower()))) { return new UserException(login.UserAlreadyExists()); } + var accountId = Guid.NewGuid().ToString(); + var account = new Account { + Id = accountId, Login = login, Password = password.EncodeBase64(), - Statistics = new Statistics() }; - _accounts.Add(account); - await _repository.SaveChangesAsync(); + _repository.Accounts.Add(account); - // var accountStatistics = new Statistics - // { - // AccountId = account.Id - // }; - // - // await _repository.StatisticsEnumerable.AddAsync(accountStatistics); - // await _repository.SaveChangesAsync(); - // - // account.StatisticsId = accountStatistics.Id; - // - // await _repository.SaveChangesAsync(); + var accountStatistics = new Statistics + { + Id = accountId, + AccountId = accountId + }; + _repository.StatisticsEnumerable.Add(accountStatistics); + await _repository.SaveChangesAsync(); + return StatusCodes.Status200OK; } catch @@ -104,19 +103,19 @@ public async Task> /// public async Task> LoginAsync(string login, string password) { - var userAccount = await _accounts.FirstOrDefaultAsync(account => account.Login == login); + var userAccount = await _repository.Accounts.FirstOrDefaultAsync(account => account.Login.ToLower().Equals(login.ToLower())); if (userAccount is null) { return new UserException(login.UserNotFound()); } - if (_attemptValidationService.IsCoolDown(login, out var coolRequestDate)) + if (login.IsCoolDown(out var coolRequestDate)) { return new UserException(login.UserCoolDown(coolRequestDate)); } - if (userAccount.Password.IsHashEqual(login)) + if (userAccount.Password.IsHashEqual(password)) { return new AccountOutputModel { @@ -124,48 +123,46 @@ public async Task> LoginAsync(string lo Login = userAccount.Login }; } - - _attemptValidationService.TryInsertFailAttempt(login); + + login.TryInsertFailAttempt(); return new UserException(login.UserInvalidCredentials()); } - public Task RemoveAsync(int accountId) + public Task RemoveAsync(string accountId) { - throw new NotImplementedException(); + _repository.Accounts.Remove(new Account { Id = accountId }); + + return _repository.SaveChangesAsync(); } private string BuildToken(Account accountModel) - { - var identity = GetClaimsIdentity(accountModel.Id); - + { var now = DateTime.UtcNow; - var jwtToken = new JwtSecurityToken( - issuer: _authOptions.Issuer, - audience: _authOptions.Audience, - notBefore: now, - claims: identity.Claims, - expires: now.Add(_authOptions.LifeTime), - signingCredentials: new SigningCredentials( - _authOptions.GetSymmetricSecurityKey(), - SecurityAlgorithms.HmacSha256)); - - var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwtToken); + + var encodedJwt = TokenHandler.CreateEncodedJwt( + _authOptions.Issuer, + _authOptions.Audience, + GetClaimsIdentity(accountModel.Id), + now, + now.Add(_authOptions.LifeTime), + now, + _signingCredentials); return encodedJwt; } - private static ClaimsIdentity GetClaimsIdentity(int userId) + private static ClaimsIdentity GetClaimsIdentity(string userId) { var claims = new[] { - new Claim("id", userId.ToString()), - new Claim(ClaimsIdentity.DefaultRoleClaimType, "User") + new Claim(ClaimsIdentity.DefaultNameClaimType, userId), + new Claim(ClaimsIdentity.DefaultRoleClaimType, Roles.User) }; var claimsIdentity = new ClaimsIdentity( claims, - "Token", + JwtBearerDefaults.AuthenticationScheme, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); diff --git a/Server.Authentication/Services/IAuthService.cs b/Server.Authentication/Services/IAuthService.cs index 4af6791..8f2fa17 100644 --- a/Server.Authentication/Services/IAuthService.cs +++ b/Server.Authentication/Services/IAuthService.cs @@ -8,5 +8,6 @@ namespace Server.Authentication.Services; public interface IAuthService { Task> RegisterAsync(string login, string password); + Task> LoginAsync(string login, string password); } \ No newline at end of file diff --git a/Server.Bll/Exceptions/CustomException.cs b/Server.Bll/Exceptions/CustomException.cs index b0849db..1ed71f7 100644 --- a/Server.Bll/Exceptions/CustomException.cs +++ b/Server.Bll/Exceptions/CustomException.cs @@ -2,7 +2,7 @@ namespace Server.Bll.Exceptions; -public readonly struct CustomException +public sealed class CustomException { public int Code { get;} public string Message { get; } @@ -12,10 +12,4 @@ public CustomException(string template, int code = StatusCodes.Status400BadReque Message = template; Code = code; } - - public CustomException(string template, string customObject, int code = StatusCodes.Status400BadRequest) - { - Message = string.Format(template, customObject); - Code = code; - } } \ No newline at end of file diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index 3019b07..143886a 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -1,6 +1,6 @@ namespace Server.Bll.Exceptions; -public static class ExceptionTemplates +internal static class ExceptionTemplates { // GENERAL EXCEPTION MESSAGES public const string Unknown = "Unknown error occured. Please try again"; diff --git a/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server.Bll/Extensions/ServiceCollectionExtensions.cs index 3676d6b..851f474 100644 --- a/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -13,6 +13,7 @@ public static IServiceCollection AddBusinessLogic(this IServiceCollection servic .AddTransient() .AddTransient() .AddHostedService(); + service.AddHttpContextAccessor(); service diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index 2d032a5..3bb7361 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -26,6 +26,8 @@ public CleanerHostedService( public Task StartAsync(CancellationToken cancellationToken) { + _logger.LogInformation("Starting Cleaning"); + _timer = new Timer( CleanJunk, _serviceProvider, @@ -37,16 +39,15 @@ public Task StartAsync(CancellationToken cancellationToken) private async void CleanJunk(object state) { - _logger.LogInformation("Starting Cleaning"); - var factory = (IServiceScopeFactory) state; using var scope = factory.CreateScope(); var roomService = scope.ServiceProvider.GetRequiredService(); //todo: timespan to option. var rooms = await roomService .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); - - _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); + + if(rooms > 0) + _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); } public Task StopAsync(CancellationToken cancellationToken) diff --git a/Server.Bll/Models/StatisticsModel.cs b/Server.Bll/Models/StatisticsModel.cs index b3783a2..c36557c 100644 --- a/Server.Bll/Models/StatisticsModel.cs +++ b/Server.Bll/Models/StatisticsModel.cs @@ -2,6 +2,9 @@ public sealed class StatisticsModel { + /// + /// . + /// public AccountModel Account { get; set; } /// diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 9de1dd0..5f44dbc 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -5,10 +5,10 @@ - + - + diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs index 659dfb8..82a55a5 100644 --- a/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ b/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; using System.Threading.Tasks; +using OneOf; +using Server.Bll.Exceptions; using Server.Bll.Models; namespace Server.Bll.Services.Interfaces; public interface IStatisticsService { - Task> GetAllStatistics(); - Task GetPersonalStatistics(int userId); + Task GetAllStatistics(); + Task> GetPersonalStatistics(string userId); } \ No newline at end of file diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index acce3ff..fdac520 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; using System.Threading.Tasks; using Mapster; using Microsoft.EntityFrameworkCore; +using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using Server.Dal.Context; +using OneOf; namespace Server.Bll.Services; @@ -14,23 +16,25 @@ internal sealed class StatisticsService : IStatisticsService public StatisticsService(ServerContext repository) { - _repository = repository; + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); } - public async Task> GetAllStatistics() + public Task GetAllStatistics() { - return await _repository + return _repository .StatisticsEnumerable .ProjectToType() .ToArrayAsync(); } - public async Task GetPersonalStatistics(int userId) + public async Task> GetPersonalStatistics(string userId) { var statistics = await _repository.StatisticsEnumerable - .Include(x=> x.Account) - .FirstOrDefaultAsync(statistics => statistics.Id == userId); - - return statistics.Adapt(); + .Include(stats => stats.Account) + .FirstOrDefaultAsync(statistics => statistics.Id.Equals(userId)); + + return statistics is not null + ? statistics.Adapt() + : new CustomException($"Unable to get statistics for user \"{userId}\""); } } \ No newline at end of file diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Dal/Context/ServerContext.cs index aef3f24..1a3f3d2 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Dal/Context/ServerContext.cs @@ -7,7 +7,8 @@ public sealed class ServerContext : DbContext { public DbSet Accounts { get; set; } public DbSet Rooms { get; set; } - public DbSet RoomPlayersEnumerable { get; set; } + + public DbSet Players { get; set; } public DbSet Rounds { get; set; } public DbSet StatisticsEnumerable { get; set; } @@ -16,35 +17,7 @@ public ServerContext(DbContextOptions contextOptions) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() .HasQueryFilter(x => !x.IsFinished); - - // modelBuilder.Entity() - // .HasOne(invite => invite.FirstPlayer) - // .WithMany(user => user.FirstPlayer) - // .HasForeignKey(invite => invite.FirstPlayerId) - // .OnDelete(DeleteBehavior.NoAction); - // - // modelBuilder.Entity() - // .HasOne(players => players.Room) - // .WithOne() - // .OnDelete(DeleteBehavior.Cascade); - // - // modelBuilder.Entity() - // .HasOne(x => x.RoomPlayer) - // .WithOne() - // .OnDelete(DeleteBehavior.Cascade); - // - // modelBuilder.Entity() - // .HasOne(invite => invite.SecondPlayer) - // .WithMany(user => user.SecondPlayer) - // .HasForeignKey(invite => invite.SecondPlayerId) - // .OnDelete(DeleteBehavior.NoAction); - // - // modelBuilder.Entity() - // .HasOne(x => x.RoomPlayer) - // .WithOne() - // .OnDelete(DeleteBehavior.NoAction); } } \ No newline at end of file diff --git a/Server.Dal/Entities/Account.cs b/Server.Dal/Entities/Account.cs index da87113..8ddc5ec 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Dal/Entities/Account.cs @@ -10,8 +10,8 @@ public class Account /// Id of account. Unique to everyone and similar with Statistics Id /// [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } /// /// Nick name of Account. diff --git a/Server.Dal/Entities/Player.cs b/Server.Dal/Entities/Player.cs index 45f8c2e..2a85cd0 100644 --- a/Server.Dal/Entities/Player.cs +++ b/Server.Dal/Entities/Player.cs @@ -7,11 +7,11 @@ namespace Server.Dal.Entities; public class Player { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } [ForeignKey("Account")] - public int AccountId { get; set; } + public string AccountId { get; set; } public virtual Account Account { get; set; } diff --git a/Server.Dal/Entities/Room.cs b/Server.Dal/Entities/Room.cs index 4d72db1..eaa294b 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Dal/Entities/Room.cs @@ -10,8 +10,8 @@ public class Room /// Id of the room. Consists of 5 randomized chars /// [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } /// /// Special code to join a room diff --git a/Server.Dal/Entities/Round.cs b/Server.Dal/Entities/Round.cs index 826073d..1c0dde9 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Dal/Entities/Round.cs @@ -8,11 +8,11 @@ namespace Server.Dal.Entities; public class Round { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; init; } [ForeignKey("Room")] - public int RoomId { get; set; } + public string RoomId { get; set; } public virtual Room Room { get; set; } diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Dal/Entities/Statistics.cs index 3171af1..3c12b48 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Dal/Entities/Statistics.cs @@ -8,11 +8,11 @@ namespace Server.Dal.Entities; public class Statistics { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; set; } [ForeignKey("Account")] - public int AccountId { get; set; } + public string AccountId { get; set; } public virtual Account Account { get; set; } diff --git a/Server.Dal/Extensions/SeedingExtension.cs b/Server.Dal/Extensions/SeedingExtension.cs index 9572097..4de848b 100644 --- a/Server.Dal/Extensions/SeedingExtension.cs +++ b/Server.Dal/Extensions/SeedingExtension.cs @@ -12,15 +12,23 @@ public static class SeedingExtension public static async Task EnsureBotCreated(this ServerContext context) { - var bot = await context.Accounts.FirstOrDefaultAsync(account => account.Login.Equals(DefaultName, StringComparison.OrdinalIgnoreCase)); + if (await context.Accounts.ContainsAsync(new Account {Id = DefaultName})) + { + return; + } - if (bot is null) - await context.AddAsync(new Account - { - Id = 0, - Login = DefaultName, - Password = "SKJSDKBNDFB21321412UIWHFDKSJGNKSDJGN" - }); + context.Add(new Account + { + Id = DefaultName, + Login = DefaultName, + Password = Guid.NewGuid().ToString() + }); + + context.Add(new Statistics + { + Id = DefaultName, + AccountId = DefaultName + }); await context.SaveChangesAsync(); } diff --git a/Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs b/Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs deleted file mode 100644 index f58fc02..0000000 --- a/Server.Dal/Migrations/20220507084026_InitialCommit.Designer.cs +++ /dev/null @@ -1,239 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507084026_InitialCommit")] - partial class InitialCommit - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("PlayerId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.Player", "Player") - .WithMany() - .HasForeignKey("PlayerId"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Dal.Entities.Round", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20220507084026_InitialCommit.cs b/Server.Dal/Migrations/20220507084026_InitialCommit.cs deleted file mode 100644 index 28006cd..0000000 --- a/Server.Dal/Migrations/20220507084026_InitialCommit.cs +++ /dev/null @@ -1,209 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class InitialCommit : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Accounts", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Login = table.Column(type: "TEXT", nullable: true), - Password = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Accounts", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Statistics", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AccountId = table.Column(type: "INTEGER", nullable: false), - Wins = table.Column(type: "INTEGER", nullable: false), - Loss = table.Column(type: "INTEGER", nullable: false), - Draws = table.Column(type: "INTEGER", nullable: false), - WinLossRatio = table.Column(type: "REAL", nullable: false), - TimeSpent = table.Column(type: "TEXT", nullable: true), - UsedRock = table.Column(type: "INTEGER", nullable: false), - UsedPaper = table.Column(type: "INTEGER", nullable: false), - UsedScissors = table.Column(type: "INTEGER", nullable: false), - Score = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Statistics", x => x.Id); - table.ForeignKey( - name: "FK_Statistics_Accounts_AccountId", - column: x => x.AccountId, - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Players", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AccountId = table.Column(type: "INTEGER", nullable: false), - Move = table.Column(type: "INTEGER", nullable: false), - RoundId = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Players", x => x.Id); - table.ForeignKey( - name: "FK_Players_Accounts_AccountId", - column: x => x.AccountId, - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Rooms", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Code = table.Column(type: "TEXT", nullable: true), - PlayerId = table.Column(type: "INTEGER", nullable: true), - IsPrivate = table.Column(type: "INTEGER", nullable: false), - IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Rooms", x => x.Id); - table.ForeignKey( - name: "FK_Rooms_Players_PlayerId", - column: x => x.PlayerId, - principalTable: "Players", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Rounds", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoomId = table.Column(type: "INTEGER", nullable: false), - WinnerId = table.Column(type: "INTEGER", nullable: true), - LoserId = table.Column(type: "INTEGER", nullable: true), - IsFinished = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Rounds", x => x.Id); - table.ForeignKey( - name: "FK_Rounds_Accounts_LoserId", - column: x => x.LoserId, - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - column: x => x.WinnerId, - principalTable: "Accounts", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Rounds_Rooms_RoomId", - column: x => x.RoomId, - principalTable: "Rooms", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Players_AccountId", - table: "Players", - column: "AccountId"); - - migrationBuilder.CreateIndex( - name: "IX_Players_RoundId", - table: "Players", - column: "RoundId"); - - migrationBuilder.CreateIndex( - name: "IX_Rooms_PlayerId", - table: "Rooms", - column: "PlayerId"); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_LoserId", - table: "Rounds", - column: "LoserId"); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_RoomId", - table: "Rounds", - column: "RoomId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Rounds_WinnerId", - table: "Rounds", - column: "WinnerId"); - - migrationBuilder.CreateIndex( - name: "IX_Statistics_AccountId", - table: "Statistics", - column: "AccountId", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Players_Rounds_RoundId", - table: "Players", - column: "RoundId", - principalTable: "Rounds", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Players_Accounts_AccountId", - table: "Players"); - - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds"); - - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds"); - - migrationBuilder.DropForeignKey( - name: "FK_Players_Rounds_RoundId", - table: "Players"); - - migrationBuilder.DropTable( - name: "Statistics"); - - migrationBuilder.DropTable( - name: "Accounts"); - - migrationBuilder.DropTable( - name: "Rounds"); - - migrationBuilder.DropTable( - name: "Rooms"); - - migrationBuilder.DropTable( - name: "Players"); - } - } -} diff --git a/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs b/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs deleted file mode 100644 index 5dc42ba..0000000 --- a/Server.Dal/Migrations/20220507084330_FixStatistics.Designer.cs +++ /dev/null @@ -1,239 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507084330_FixStatistics")] - partial class FixStatistics - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("PlayerId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.Player", "Player") - .WithMany() - .HasForeignKey("PlayerId"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Dal.Entities.Round", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Migrations/20220507084330_FixStatistics.cs b/Server.Dal/Migrations/20220507084330_FixStatistics.cs deleted file mode 100644 index 53fc054..0000000 --- a/Server.Dal/Migrations/20220507084330_FixStatistics.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Server.Dal.Migrations -{ - public partial class FixStatistics : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "TimeSpent", - table: "Statistics", - type: "TEXT", - nullable: false, - defaultValue: new TimeSpan(0, 0, 0, 0, 0), - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "TimeSpent", - table: "Statistics", - type: "TEXT", - nullable: true, - oldClrType: typeof(TimeSpan), - oldType: "TEXT"); - } - } -} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs deleted file mode 100644 index 9032835..0000000 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ /dev/null @@ -1,237 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - partial class ServerContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("PlayerId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("INTEGER"); - - b.Property("WinnerId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AccountId") - .HasColumnType("INTEGER"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Player", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.HasOne("Server.Dal.Entities.Player", "Player") - .WithMany() - .HasForeignKey("PlayerId"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Dal.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Dal.Entities.Round", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.Dal.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => - { - b.HasOne("Server.Dal.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Room", b => - { - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Dal/Server.Dal.csproj b/Server.Dal/Server.Dal.csproj index 63d4456..5cce87f 100644 --- a/Server.Dal/Server.Dal.csproj +++ b/Server.Dal/Server.Dal.csproj @@ -5,12 +5,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 18e182e..5ba0de1 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -19,13 +20,14 @@ public sealed class RoomController : ControllerBase { private readonly IRoomService _roomService; private readonly IApplicationUser _applicationUser; - private int UserId => _applicationUser.Id; + private string UserId => _applicationUser.Id; - public RoomController(IRoomService roomService, + public RoomController( + IRoomService roomService, IApplicationUser applicationUser) { - _roomService = roomService; - _applicationUser = applicationUser; + _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService)); + _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } [HttpPost("create")] @@ -34,31 +36,34 @@ public RoomController(IRoomService roomService, public async Task CreateRoom([FromQuery] bool isPrivate, [FromHeader(Name="X-Training")] bool isTraining = false) { - var newRoom = await _roomService - .CreateAsync(UserId, isPrivate, isTraining); - - return newRoom.Match( - Ok, - exception => BadRequest(exception)); + throw new NotImplementedException(); + // var newRoom = await _roomService + // .CreateAsync(UserId, isPrivate, isTraining); + // + // return newRoom.Match( + // Ok, + // exception => BadRequest(exception)); } [HttpPost("join/public")] public async Task JoinPublicRoom() { - var result = await _roomService.JoinAsync(UserId, true,null); - return result.Match( - Ok, - exception => BadRequest(exception)); + throw new NotImplementedException(); + // var result = await _roomService.JoinAsync(UserId, true,null); + // return result.Match( + // Ok, + // exception => BadRequest(exception)); } [HttpPost("join/private")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task JoinPrivateRoom([FromQuery] string roomCode) { - var result = await _roomService.JoinAsync(UserId, false, roomCode); - return result.Match( - Ok, - exception => BadRequest(exception)); + throw new NotImplementedException(); + // var result = await _roomService.JoinAsync(UserId, false, roomCode); + // return result.Match( + // Ok, + // exception => BadRequest(exception)); } [HttpGet("update")] @@ -66,25 +71,27 @@ public async Task JoinPrivateRoom([FromQuery] string roomCode) [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateRoom([FromBody] RoomModel roomModel) { - var updateResponse = await _roomService.UpdateRoom(roomModel); - - return updateResponse switch - { - 200 => Ok(), - _ => BadRequest() - }; + throw new NotImplementedException(); + // var updateResponse = await _roomService.UpdateRoom(roomModel); + // + // return updateResponse switch + // { + // 200 => Ok(), + // _ => BadRequest() + // }; } [HttpDelete("delete")] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task DeleteRoom([FromQuery] int roomId) { - var deleteResponse = await _roomService.DeleteAsync(UserId,roomId); - - return deleteResponse switch - { - 200 => Ok(), - _ => BadRequest() - }; + throw new NotImplementedException(); + // var deleteResponse = await _roomService.DeleteAsync(UserId,roomId); + // + // return deleteResponse switch + // { + // 200 => Ok(), + // _ => BadRequest() + // }; } } \ No newline at end of file diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 3cff1ad..08cb892 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -21,7 +22,7 @@ public sealed class RoundController:ControllerBase { private readonly IApplicationUser _applicationUser; private readonly IRoundService _roundService; - private int UserId => _applicationUser.Id; + private string UserId => _applicationUser.Id; public RoundController( IRoundService roundService, IApplicationUser applicationUser) @@ -39,10 +40,11 @@ public RoundController( [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task CreateRound(int roomId) { - var result = await _roundService.CreateAsync(UserId, roomId); - return result.Match( - Ok, - exception => BadRequest(exception)); + throw new NotImplementedException(); + // var result = await _roundService.CreateAsync(UserId, roomId); + // return result.Match( + // Ok, + // exception => BadRequest(exception)); } /// /// Updates current room (Patches). @@ -54,9 +56,10 @@ public async Task CreateRound(int roomId) [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateCurrentRound(RoundModel roundModel) { - var updateResult = await _roundService.UpdateAsync(UserId, roundModel); - return updateResult.Match( - Ok, - exception => BadRequest(exception)); + throw new NotImplementedException(); + // var updateResult = await _roundService.UpdateAsync(UserId, roundModel); + // return updateResult.Match( + // Ok, + // exception => BadRequest(exception)); } } \ No newline at end of file diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 3fdc3ba..b55a6a0 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Server.Authentication.Models.Interfaces; +using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using Server.Host.Contracts; @@ -22,28 +23,34 @@ public sealed class StatisticsController : ControllerBase { private readonly IStatisticsService _statisticsService; private readonly IApplicationUser _applicationUser; - private int UserId => _applicationUser.Id; + + private string UserId => _applicationUser.Id; + public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) { _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } - [HttpGet("all")] [AllowAnonymous] - [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] + [HttpGet("all")] + [ProducesResponseType(typeof(ShortStatisticsModel[]), (int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public Task> GetOverallStatistics() + public Task GetOverallStatistics() { return _statisticsService.GetAllStatistics(); } - [HttpGet("personal")] [Authorize] - //[ProducesResponseType(typeof(Statistics), (int) HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] + [HttpGet("personal")] + [ProducesResponseType(typeof(StatisticsModel), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(CustomException), (int) HttpStatusCode.BadRequest)] public async Task GetPersonalStatistics() { - return Ok(await _statisticsService.GetPersonalStatistics(UserId)); + var result = await _statisticsService.GetPersonalStatistics(UserId); + + return result.Match( + statsModel => Ok(statsModel), + statsException => BadRequest(statsException)); } } \ No newline at end of file diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 15bf859..8e4743b 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -6,15 +6,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + From 46085b79516563e73a8ceabe8241e62d3e42181d Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 7 May 2022 21:56:37 +0300 Subject: [PATCH 36/56] Adding Migration --- ...634_InitialMigrationStringKeys.Designer.cs | 229 ++++++++++++++++++ ...220507161634_InitialMigrationStringKeys.cs | 200 +++++++++++++++ .../Migrations/ServerContextModelSnapshot.cs | 227 +++++++++++++++++ 3 files changed, 656 insertions(+) create mode 100644 Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs create mode 100644 Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs create mode 100644 Server.Dal/Migrations/ServerContextModelSnapshot.cs diff --git a/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs b/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs new file mode 100644 index 0000000..d29c78a --- /dev/null +++ b/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs @@ -0,0 +1,229 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220507161634_InitialMigrationStringKeys")] + partial class InitialMigrationStringKeys + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Dal.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.Player", "Player") + .WithMany() + .HasForeignKey("PlayerId"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Dal.Entities.Round", "RoomId"); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs b/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs new file mode 100644 index 0000000..52e2d69 --- /dev/null +++ b/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs @@ -0,0 +1,200 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class InitialMigrationStringKeys : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Accounts", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Login = table.Column(type: "TEXT", nullable: true), + Password = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Accounts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Statistics", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AccountId = table.Column(type: "TEXT", nullable: true), + Wins = table.Column(type: "INTEGER", nullable: false), + Loss = table.Column(type: "INTEGER", nullable: false), + Draws = table.Column(type: "INTEGER", nullable: false), + WinLossRatio = table.Column(type: "REAL", nullable: false), + TimeSpent = table.Column(type: "TEXT", nullable: false), + UsedRock = table.Column(type: "INTEGER", nullable: false), + UsedPaper = table.Column(type: "INTEGER", nullable: false), + UsedScissors = table.Column(type: "INTEGER", nullable: false), + Score = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Statistics", x => x.Id); + table.ForeignKey( + name: "FK_Statistics_Accounts_AccountId", + column: x => x.AccountId, + principalTable: "Accounts", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Players", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + AccountId = table.Column(type: "TEXT", nullable: true), + Move = table.Column(type: "INTEGER", nullable: false), + RoundId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Players", x => x.Id); + table.ForeignKey( + name: "FK_Players_Accounts_AccountId", + column: x => x.AccountId, + principalTable: "Accounts", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Rooms", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Code = table.Column(type: "TEXT", nullable: true), + PlayerId = table.Column(type: "TEXT", nullable: true), + IsPrivate = table.Column(type: "INTEGER", nullable: false), + IsFull = table.Column(type: "INTEGER", nullable: false), + CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rooms", x => x.Id); + table.ForeignKey( + name: "FK_Rooms_Players_PlayerId", + column: x => x.PlayerId, + principalTable: "Players", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Rounds", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RoomId = table.Column(type: "TEXT", nullable: true), + WinnerId = table.Column(type: "TEXT", nullable: true), + LoserId = table.Column(type: "TEXT", nullable: true), + IsFinished = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rounds", x => x.Id); + table.ForeignKey( + name: "FK_Rounds_Accounts_LoserId", + column: x => x.LoserId, + principalTable: "Accounts", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Rounds_Accounts_WinnerId", + column: x => x.WinnerId, + principalTable: "Accounts", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Rounds_Rooms_RoomId", + column: x => x.RoomId, + principalTable: "Rooms", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Players_AccountId", + table: "Players", + column: "AccountId"); + + migrationBuilder.CreateIndex( + name: "IX_Players_RoundId", + table: "Players", + column: "RoundId"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_PlayerId", + table: "Rooms", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_LoserId", + table: "Rounds", + column: "LoserId"); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_RoomId", + table: "Rounds", + column: "RoomId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Rounds_WinnerId", + table: "Rounds", + column: "WinnerId"); + + migrationBuilder.CreateIndex( + name: "IX_Statistics_AccountId", + table: "Statistics", + column: "AccountId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Players_Rounds_RoundId", + table: "Players", + column: "RoundId", + principalTable: "Rounds", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Players_Accounts_AccountId", + table: "Players"); + + migrationBuilder.DropForeignKey( + name: "FK_Rounds_Accounts_LoserId", + table: "Rounds"); + + migrationBuilder.DropForeignKey( + name: "FK_Rounds_Accounts_WinnerId", + table: "Rounds"); + + migrationBuilder.DropForeignKey( + name: "FK_Players_Rounds_RoundId", + table: "Players"); + + migrationBuilder.DropTable( + name: "Statistics"); + + migrationBuilder.DropTable( + name: "Accounts"); + + migrationBuilder.DropTable( + name: "Rounds"); + + migrationBuilder.DropTable( + name: "Rooms"); + + migrationBuilder.DropTable( + name: "Players"); + } + } +} diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Dal/Migrations/ServerContextModelSnapshot.cs new file mode 100644 index 0000000..006773e --- /dev/null +++ b/Server.Dal/Migrations/ServerContextModelSnapshot.cs @@ -0,0 +1,227 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Dal.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + partial class ServerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Player", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Dal.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.HasOne("Server.Dal.Entities.Player", "Player") + .WithMany() + .HasForeignKey("PlayerId"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.HasOne("Server.Dal.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Dal.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Dal.Entities.Round", "RoomId"); + + b.HasOne("Server.Dal.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + { + b.HasOne("Server.Dal.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Room", b => + { + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Dal.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} From 15dd8c7ccba7731dd74ccff205d960a8073bed2a Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 7 May 2022 23:57:32 +0300 Subject: [PATCH 37/56] Fixed entities. Added migrations. Wip with services. --- RockPaperScissorsGame.sln | 2 +- .../Server.Authentication.csproj | 2 +- Server.Authentication/Services/AuthService.cs | 4 +- Server.Bll/Exceptions/ExceptionTemplates.cs | 20 +- .../HostedServices/CleanerHostedService.cs | 2 +- Server.Bll/Models/PlayerModel.cs | 21 ++ Server.Bll/Models/RoomModel.cs | 49 ++-- Server.Bll/Models/RoomPlayersModel.cs | 13 - Server.Bll/Server.Bll.csproj | 3 +- .../Services/Interfaces/IRoomService.cs | 12 +- .../Services/Interfaces/IRoundService.cs | 6 +- Server.Bll/Services/LongPollingService.cs | 2 +- Server.Bll/Services/RoomService.cs | 172 +++++++------ Server.Bll/Services/RoundService.cs | 109 ++++---- Server.Bll/Services/StatisticsService.cs | 2 +- .../Context/ServerContext.cs | 4 +- .../Entities/Account.cs | 2 +- .../Entities/Player.cs | 4 +- {Server.Dal => Server.Data}/Entities/Room.cs | 11 +- {Server.Dal => Server.Data}/Entities/Round.cs | 9 +- .../Entities/Statistics.cs | 2 +- .../Extensions/DatabaseExtension.cs | 6 +- .../Extensions/SeedingExtension.cs | 6 +- ...634_InitialMigrationStringKeys.Designer.cs | 50 ++-- ...220507161634_InitialMigrationStringKeys.cs | 0 ...07201712_PlayersCollectionRoom.Designer.cs | 71 +++--- .../20220507201712_PlayersCollectionRoom.cs | 75 ++++++ ...01850_PlayersCollectionRoomFix.Designer.cs | 226 +++++++++++++++++ ...20220507201850_PlayersCollectionRoomFix.cs | 19 ++ ...20220507202003_PlayerReadyFlag.Designer.cs | 229 +++++++++++++++++ .../20220507202003_PlayerReadyFlag.cs | 26 ++ .../20220507205137_DateEntities.Designer.cs | 235 ++++++++++++++++++ .../Migrations/20220507205137_DateEntities.cs | 38 +++ .../Migrations/ServerContextModelSnapshot.cs | 233 +++++++++++++++++ .../Server.Data.csproj | 0 Server.Host/Server.Host.csproj | 2 +- Server.Host/Startup.cs | 4 +- 37 files changed, 1402 insertions(+), 269 deletions(-) create mode 100644 Server.Bll/Models/PlayerModel.cs delete mode 100644 Server.Bll/Models/RoomPlayersModel.cs rename {Server.Dal => Server.Data}/Context/ServerContext.cs (91%) rename {Server.Dal => Server.Data}/Entities/Account.cs (95%) rename {Server.Dal => Server.Data}/Entities/Player.cs (84%) rename {Server.Dal => Server.Data}/Entities/Room.cs (78%) rename {Server.Dal => Server.Data}/Entities/Round.cs (74%) rename {Server.Dal => Server.Data}/Entities/Statistics.cs (96%) rename {Server.Dal => Server.Data}/Extensions/DatabaseExtension.cs (84%) rename {Server.Dal => Server.Data}/Extensions/SeedingExtension.cs (89%) rename {Server.Dal => Server.Data}/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs (79%) rename {Server.Dal => Server.Data}/Migrations/20220507161634_InitialMigrationStringKeys.cs (100%) rename Server.Dal/Migrations/ServerContextModelSnapshot.cs => Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs (74%) create mode 100644 Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs create mode 100644 Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs create mode 100644 Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs create mode 100644 Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs create mode 100644 Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs create mode 100644 Server.Data/Migrations/20220507205137_DateEntities.Designer.cs create mode 100644 Server.Data/Migrations/20220507205137_DateEntities.cs create mode 100644 Server.Data/Migrations/ServerContextModelSnapshot.cs rename Server.Dal/Server.Dal.csproj => Server.Data/Server.Data.csproj (100%) diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 128ec5c..b72ab77 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Host", "Server.Host\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{B6D69162-F9E0-4165-A288-00FF1D073291}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Dal", "Server.Dal\Server.Dal.csproj", "{1207E8F7-D6FF-4F7D-B5A1-55D96F286430}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Data", "Server.Data\Server.Data.csproj", "{1207E8F7-D6FF-4F7D-B5A1-55D96F286430}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server.Bll\Server.Bll.csproj", "{FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}" EndProject diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 81ab7be..de25ef1 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -10,7 +10,7 @@ - + diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 188efbd..cd835ec 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -12,8 +12,8 @@ using OneOf; using Server.Authentication.Exceptions; using Server.Authentication.Models; -using Server.Dal.Context; -using Server.Dal.Entities; +using Server.Data.Context; +using Server.Data.Entities; namespace Server.Authentication.Services; diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index 143886a..c99b77e 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -3,18 +3,18 @@ namespace Server.Bll.Exceptions; internal static class ExceptionTemplates { // GENERAL EXCEPTION MESSAGES - public const string Unknown = "Unknown error occured. Please try again"; - public const string NotAllowed = "You are not allowed to do this."; + internal const string Unknown = "Unknown error occured. Please try again"; + internal const string NotAllowed = "You are not allowed to do this."; // ROOM EXCEPTIONS - public const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; - public const string RoomNotExists = "Room does not exist."; - public const string RoomFull = "This room is full."; - public const string AlreadyInRoom = "You are already in room."; - public const string RoomNotFull = "Room is not full."; - public const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; + internal const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; + internal const string RoomNotExists = "Room does not exist."; + internal const string RoomFull = "This room is full."; + internal const string AlreadyInRoom = "You are already in room."; + internal const string RoomNotFull = "Room is not full."; + internal const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; // ROUND EXCEPTIONS - public const string RoundAlreadyCreated = "Round is already creaded."; - public static string RoundNotFound(int roundId) => $"Round with id \"{roundId}\" is not found"; + internal const string RoundAlreadyCreated = "Round is already creaded."; + internal static string RoundNotFound(int roundId) => $"Round with id \"{roundId}\" is not found"; } \ No newline at end of file diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index 3bb7361..020d46f 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -44,7 +44,7 @@ private async void CleanJunk(object state) var roomService = scope.ServiceProvider.GetRequiredService(); //todo: timespan to option. var rooms = await roomService - .RemoveEntityRangeByDate(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); + .RemoveRangeAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); if(rooms > 0) _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); diff --git a/Server.Bll/Models/PlayerModel.cs b/Server.Bll/Models/PlayerModel.cs new file mode 100644 index 0000000..e8c4150 --- /dev/null +++ b/Server.Bll/Models/PlayerModel.cs @@ -0,0 +1,21 @@ + +namespace Server.Bll.Models; + +public sealed class PlayerModel +{ + public PlayerModel( + string id, + string accountId, + int move) + { + Id = id; + AccountId = accountId; + Move = move; + } + + public string Id { get; init; } + + public string AccountId { get; set; } + + public int Move { get; set; } +} \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs index 12d9648..0803790 100644 --- a/Server.Bll/Models/RoomModel.cs +++ b/Server.Bll/Models/RoomModel.cs @@ -1,24 +1,39 @@ -using System; - -namespace Server.Bll.Models; +namespace Server.Bll.Models; public sealed class RoomModel { - public int Id { get; set; } - - public int? RoundId { get; set; } - - public string RoomCode { get; set; } - - public RoomPlayersModel RoomPlayers { get; set; } - + /// + /// Id of the room. Consists of 5 randomized chars + /// + public string Id { get; init; } + + /// + /// Special code to join a room + /// + public string Code { get; set; } + + /// + /// Round, linked to this room + /// + public RoundModel? Round { get; set; } + + /// + /// . + /// + public PlayerModel? Player { get; set; } + + /// + /// Flag is this room is private + /// public bool IsPrivate { get; set; } - public bool IsReady { get; set; } - + /// + /// Flag if room is full + /// public bool IsFull { get; set; } - - public DateTimeOffset CreationTime { get; set; } - - public bool IsRoundEnded { get; set; } + + /// + /// Creation date. After 5 minutes of inactivity will be deleted + /// + public long CreationTimeTicks { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomPlayersModel.cs b/Server.Bll/Models/RoomPlayersModel.cs deleted file mode 100644 index 7d4bb2e..0000000 --- a/Server.Bll/Models/RoomPlayersModel.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace Server.Bll.Models; - -public sealed class RoomPlayersModel -{ - public AccountModel FirstPlayer { get; set; } - - public int FirstPlayerMove { get; set; } - - public AccountModel SecondPlayer { get; set; } - - public int SecondPlayerMove { get; set; } -} \ No newline at end of file diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 5f44dbc..3a49436 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -2,6 +2,7 @@ net6 + enable @@ -12,7 +13,7 @@ - + diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index e34f727..05c4b3d 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -8,15 +8,15 @@ namespace Server.Bll.Services.Interfaces; public interface IRoomService { - Task> CreateAsync(int userId, bool isPrivate = false, bool isTraining = false); + Task> CreateAsync(string userId, bool isPrivate = false, bool isTraining = false); - Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate); + Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate); - Task> JoinAsync(int userId, bool isPrivate, string roomCode); + Task> JoinAsync(string userId, bool isPrivate, string roomCode); - Task> GetAsync(int roomId); + Task> GetAsync(string roomId); - Task UpdateRoom(RoomModel room); + Task UpdateAsync(RoomModel room); - Task DeleteAsync(int userId, int roomId); + Task DeleteAsync(string userId, string roomId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server.Bll/Services/Interfaces/IRoundService.cs index f7cf5b2..8c655c8 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server.Bll/Services/Interfaces/IRoundService.cs @@ -7,7 +7,9 @@ namespace Server.Bll.Services.Interfaces; public interface IRoundService { - Task> CreateAsync(int userId, int roomId); + Task> CreateAsync(string userId, string roomId); + Task MakeMoveAsync(); - Task> UpdateAsync(int userId, RoundModel roundModel); + + Task> UpdateAsync(string userId, RoundModel roundModel); } \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index c2b94be..dd6241d 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Server.Bll.Services.Interfaces; -using Server.Dal.Context; +using Server.Data.Context; namespace Server.Bll.Services; diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index c60af14..0f552b7 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -2,80 +2,80 @@ using System.Linq; using System.Threading.Tasks; using Mapster; -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; using OneOf; using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using Server.Dal.Context; -using Server.Dal.Entities; +using Server.Data.Context; +using Server.Data.Entities; namespace Server.Bll.Services; internal sealed class RoomService : IRoomService { - private readonly DbSet _rooms; private readonly ServerContext _repository; private readonly IRoundService _roundService; - public RoomService(ServerContext repository, + public RoomService( + ServerContext repository, IRoundService roundService) { - _repository = repository; - _roundService = roundService; - _rooms = _repository.Rooms; + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); } public async Task> - CreateAsync(int userId, bool isPrivate = false, bool isTraining = false) + CreateAsync(string userId, bool isPrivate = false, bool isTraining = false) { throw new NotImplementedException(); - // var doesRoomExist = await _repository.RoomPlayersEnumerable - // .AnyAsync(roomPlayers => roomPlayers.FirstPlayerId == userId - // || roomPlayers.SecondPlayerId == userId); - // - // if (doesRoomExist) - // { - // return new CustomException(ExceptionTemplates.TwinkRoom); - // } - // - // var room = new Room - // { - // IsPrivate = isPrivate, - // Code = Guid.NewGuid().ToString("n")[..8], - // IsFull = false, - // CreationTimeTicks = DateTimeOffset.Now.Ticks, - // Player = new Player - // { - // FirstPlayerId = userId, - // Count = 1 - // } - // }; - // - // // await _rooms.AddAsync(room); - // // await _repository.SaveChangesAsync(); - // - // // var newRoomPlayers = new RoomPlayers + // throw new NotImplementedException(); + // // var doesRoomExist = await _repository.RoomPlayersEnumerable + // // .AnyAsync(roomPlayers => roomPlayers.FirstPlayerId == userId + // // || roomPlayers.SecondPlayerId == userId); + // // + // // if (doesRoomExist) // // { - // // RoomId = room.Id, - // // FirstPlayerId = userId, - // // PlayersCount = 1 - // // }; + // // return new CustomException(ExceptionTemplates.TwinkRoom); + // // } // // - // if (isTraining) - // { - // room.Player.SecondPlayerId = 0; - // } - // - // _repository.Add(room); - // - // // room.RoomPlayers = newRoomPlayers; - // // _rooms.Update(room); + // // var room = new Room + // // { + // // IsPrivate = isPrivate, + // // Code = Guid.NewGuid().ToString("n")[..8], + // // IsFull = false, + // // CreationTimeTicks = DateTimeOffset.Now.Ticks, + // // Player = new Player + // // { + // // FirstPlayerId = userId, + // // Count = 1 + // // } + // // }; + // // + // // // await _rooms.AddAsync(room); + // // // await _repository.SaveChangesAsync(); // // - // await _repository.SaveChangesAsync(); - // - // return room.Adapt(); + // // // var newRoomPlayers = new RoomPlayers + // // // { + // // // RoomId = room.Id, + // // // FirstPlayerId = userId, + // // // PlayersCount = 1 + // // // }; + // // // + // // if (isTraining) + // // { + // // room.Player.SecondPlayerId = 0; + // // } + // // + // // _repository.Add(room); + // // + // // // room.RoomPlayers = newRoomPlayers; + // // // _rooms.Update(room); + // // // + // // await _repository.SaveChangesAsync(); + // // + // // return room.Adapt(); } public async Task> JoinAsync(int userId, bool isPrivate, string roomCode) @@ -135,14 +135,20 @@ public async Task> JoinAsync(int userId, bool public async Task> GetAsync(int roomId) { - var room = await _rooms.FindAsync(roomId); + throw new NotImplementedException(); + // var room = await _rooms.FindAsync(roomId); + // + // return room is not null + // ? room.Adapt() + // : new CustomException(ExceptionTemplates.RoomNotExists); + } - return room is not null - ? room.Adapt() - : new CustomException(ExceptionTemplates.RoomNotExists); + public Task> GetAsync(string roomId) + { + throw new NotImplementedException(); } - - public async Task UpdateRoom(RoomModel room) + + public async Task UpdateAsync(RoomModel room) { throw new NotImplementedException(); // var thisRoom = await _rooms.FindAsync(room.Id); @@ -168,6 +174,11 @@ public async Task> GetAsync(int roomId) // return StatusCodes.Status200OK; } + public Task DeleteAsync(string userId, string roomId) + { + throw new NotImplementedException(); + } + public async Task DeleteAsync(int userId, int roomId) { throw new NotImplementedException(); @@ -197,32 +208,29 @@ public async Task> GetAsync(int roomId) // return StatusCodes.Status200OK; } - /// - /// Only to use with I HOSTED SERVICE! - /// - /// - /// - /// - [Obsolete(message:"Should be carefully used")] - public async Task RemoveEntityRangeByDate(TimeSpan roomOutDate, TimeSpan roundOutDate) + public async Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate) + { + var currentDate = DateTimeOffset.Now.Ticks; + + var rooms = await _repository.Rooms + .Include(room => room.Round) + .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.Round == null) + .ToArrayAsync(); + + var allRounds = await _repository.Rounds + .Where(round => round.FinishTime.Ticks + roundOutDate.Ticks < currentDate) + .ToArrayAsync(); + + _repository.Rooms.RemoveRange(rooms); + _repository.Rounds.RemoveRange(allRounds); + + await _repository.SaveChangesAsync(); + + return rooms.Length + allRounds.Length; + } + + public Task> JoinAsync(string userId, bool isPrivate, string roomCode) { - return 0; throw new NotImplementedException(); - // var currentDate = DateTimeOffset.Now.Ticks; - // - // var rooms = await _rooms - // .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.RoundId == null) - // .ToArrayAsync(); - // - // var allRound = await _repository.Rounds - // .Where(x => x.LastMoveTicks + roundOutDate.Ticks < currentDate) - // .ToArrayAsync(); - // - // _rooms.RemoveRange(rooms); - // _repository.Rounds.RemoveRange(allRound); - // - // await _repository.SaveChangesAsync(); - // - // return rooms.Length + allRound.Length; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index be1a3b2..3cdf28c 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Mapster; @@ -6,9 +7,9 @@ using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using Server.Dal.Context; using OneOf; -using Server.Dal.Entities; +using Server.Data.Context; +using Server.Data.Entities; namespace Server.Bll.Services; @@ -18,58 +19,57 @@ internal sealed class RoundService : IRoundService public RoundService(ServerContext serverContext) { - _serverContext = serverContext; + _serverContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); } - public async Task> CreateAsync(int userId, int roomId) + public async Task> CreateAsync(string userId, string roomId) { - throw new NotImplementedException(); - // var foundRoom = await _serverContext - // .Rooms - // .Include(x => x.RoomPlayer) - // .FirstOrDefaultAsync(x => x.Id == roomId); - // - // if (foundRoom is null) - // { - // return new CustomException(ExceptionTemplates.RoomNotExists); - // } - // - // if (foundRoom.RoomPlayer.FirstPlayerId != userId) - // { - // if(foundRoom.RoomPlayer.SecondPlayerId != userId) - // { - // return new CustomException(ExceptionTemplates.NotAllowed); - // } - // - // } - // if (foundRoom.RoomPlayer.SecondPlayerId != userId) - // { - // if(foundRoom.RoomPlayer.FirstPlayerId != userId) - // { - // return new CustomException(ExceptionTemplates.NotAllowed); - // } - // } - // - // if (!foundRoom.IsFull) - // { - // return new CustomException(ExceptionTemplates.RoomNotFull); - // } - // - // var newRound = new Round - // { - // RoomPlayersId = foundRoom.RoomPlayer.Id, - // FirstPlayerMove = 0, - // SecondPlayerMove = 0, - // LastMoveTicks = DateTimeOffset.Now.Ticks, - // TimeFinishedTicks = 0, - // IsFinished = false - // }; - // - // await _serverContext.AddAsync(newRound); - // - // foundRoom.RoundId = newRound.Id; - // await _serverContext.SaveChangesAsync(); - // - // return newRound.Adapt(); + var playingRoom = await _serverContext + .Rooms + .Include(rooms => rooms.Players) + .FirstOrDefaultAsync(room => room.Id.Equals(roomId)); + + if (playingRoom is null) + { + return new CustomException(ExceptionTemplates.RoomNotExists); + } + + if (!playingRoom.IsFull) + { + return new CustomException(ExceptionTemplates.RoomNotFull); + } + + if (!playingRoom.Players.Any(player => player.AccountId.Equals(userId))) + { + return new CustomException(ExceptionTemplates.NotAllowed); + } + + var newRound = new Round + { + Id = Guid.NewGuid().ToString(), + RoomId = roomId, + Room = playingRoom, + StartTime = DateTimeOffset.UtcNow, + IsFinished = false + }; + + _serverContext.Rounds.Add(newRound); + + var players = new List(2) + { + new() + { + Id = Guid.NewGuid().ToString(), + AccountId = userId, + } + }; + + newRound.Players = players; + + _serverContext.Rounds.Update(newRound); + + await _serverContext.SaveChangesAsync(); + + return newRound.Adapt(); } [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] @@ -78,6 +78,11 @@ public Task MakeMoveAsync() throw new NotImplementedException(); } + public Task> UpdateAsync(string userId, RoundModel roundModel) + { + throw new NotImplementedException(); + } + public async Task> UpdateAsync(int userId, RoundModel roundModel) { throw new NotImplementedException(); diff --git a/Server.Bll/Services/StatisticsService.cs b/Server.Bll/Services/StatisticsService.cs index fdac520..b5b8b13 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server.Bll/Services/StatisticsService.cs @@ -5,8 +5,8 @@ using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using Server.Dal.Context; using OneOf; +using Server.Data.Context; namespace Server.Bll.Services; diff --git a/Server.Dal/Context/ServerContext.cs b/Server.Data/Context/ServerContext.cs similarity index 91% rename from Server.Dal/Context/ServerContext.cs rename to Server.Data/Context/ServerContext.cs index 1a3f3d2..7b907f9 100644 --- a/Server.Dal/Context/ServerContext.cs +++ b/Server.Data/Context/ServerContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Server.Dal.Entities; +using Server.Data.Entities; -namespace Server.Dal.Context; +namespace Server.Data.Context; public sealed class ServerContext : DbContext { diff --git a/Server.Dal/Entities/Account.cs b/Server.Data/Entities/Account.cs similarity index 95% rename from Server.Dal/Entities/Account.cs rename to Server.Data/Entities/Account.cs index 8ddc5ec..bd6d2d8 100644 --- a/Server.Dal/Entities/Account.cs +++ b/Server.Data/Entities/Account.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities; +namespace Server.Data.Entities; [Table("Accounts")] public class Account diff --git a/Server.Dal/Entities/Player.cs b/Server.Data/Entities/Player.cs similarity index 84% rename from Server.Dal/Entities/Player.cs rename to Server.Data/Entities/Player.cs index 2a85cd0..69cf562 100644 --- a/Server.Dal/Entities/Player.cs +++ b/Server.Data/Entities/Player.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities; +namespace Server.Data.Entities; [Table("Players")] public class Player @@ -15,5 +15,7 @@ public class Player public virtual Account Account { get; set; } + public bool IsReady { get; set; } + public int Move { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Room.cs b/Server.Data/Entities/Room.cs similarity index 78% rename from Server.Dal/Entities/Room.cs rename to Server.Data/Entities/Room.cs index eaa294b..2f82ad4 100644 --- a/Server.Dal/Entities/Room.cs +++ b/Server.Data/Entities/Room.cs @@ -1,7 +1,8 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities; +namespace Server.Data.Entities; [Table("Rooms")] public class Room @@ -22,7 +23,11 @@ public class Room /// Round, linked to this room /// public virtual Round Round { get; set; } - public virtual Player Player { get; set; } + + /// + /// . + /// + public virtual ICollection Players { get; set; } /// /// Flag is this room is private diff --git a/Server.Dal/Entities/Round.cs b/Server.Data/Entities/Round.cs similarity index 74% rename from Server.Dal/Entities/Round.cs rename to Server.Data/Entities/Round.cs index 1c0dde9..2c5663a 100644 --- a/Server.Dal/Entities/Round.cs +++ b/Server.Data/Entities/Round.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities; +namespace Server.Data.Entities; [Table("Rounds")] public class Round @@ -21,6 +22,10 @@ public class Round public virtual Account Winner { get; set; } public virtual Account Loser { get; set; } + + public DateTimeOffset StartTime { get; set; } + + public DateTimeOffset FinishTime { get; set; } public bool IsFinished { get; set; } } \ No newline at end of file diff --git a/Server.Dal/Entities/Statistics.cs b/Server.Data/Entities/Statistics.cs similarity index 96% rename from Server.Dal/Entities/Statistics.cs rename to Server.Data/Entities/Statistics.cs index 3c12b48..9343a0d 100644 --- a/Server.Dal/Entities/Statistics.cs +++ b/Server.Data/Entities/Statistics.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Server.Dal.Entities; +namespace Server.Data.Entities; [Table("Statistics")] public class Statistics diff --git a/Server.Dal/Extensions/DatabaseExtension.cs b/Server.Data/Extensions/DatabaseExtension.cs similarity index 84% rename from Server.Dal/Extensions/DatabaseExtension.cs rename to Server.Data/Extensions/DatabaseExtension.cs index 891d3f4..300e461 100644 --- a/Server.Dal/Extensions/DatabaseExtension.cs +++ b/Server.Data/Extensions/DatabaseExtension.cs @@ -1,14 +1,14 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Server.Dal.Context; +using Server.Data.Context; -namespace Server.Dal.Extensions; +namespace Server.Data.Extensions; public static class DatabaseExtension { private const string DatabaseConnection = "DatabaseConnection"; - private const string MigrationAssemblyName = "Server.Dal"; + private const string MigrationAssemblyName = "Server.Data"; public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { return service.AddDbContext( diff --git a/Server.Dal/Extensions/SeedingExtension.cs b/Server.Data/Extensions/SeedingExtension.cs similarity index 89% rename from Server.Dal/Extensions/SeedingExtension.cs rename to Server.Data/Extensions/SeedingExtension.cs index 4de848b..15a0707 100644 --- a/Server.Dal/Extensions/SeedingExtension.cs +++ b/Server.Data/Extensions/SeedingExtension.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Server.Dal.Context; -using Server.Dal.Entities; +using Server.Data.Context; +using Server.Data.Entities; -namespace Server.Dal.Extensions; +namespace Server.Data.Extensions; public static class SeedingExtension { diff --git a/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs b/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs similarity index 79% rename from Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs rename to Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs index d29c78a..85755b4 100644 --- a/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs +++ b/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs @@ -1,12 +1,14 @@ // + + +#nullable disable + using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; - -#nullable disable +using Server.Data.Context; namespace Server.Dal.Migrations { @@ -19,7 +21,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Data.Entities.Account", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -35,7 +37,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Accounts"); }); - modelBuilder.Entity("Server.Dal.Entities.Player", b => + modelBuilder.Entity("Server.Data.Entities.Player", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -58,7 +60,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Players"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Room", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -85,7 +87,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Rooms"); }); - modelBuilder.Entity("Server.Dal.Entities.Round", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -114,7 +116,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Rounds"); }); - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + modelBuilder.Entity("Server.Data.Entities.Statistics", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -157,39 +159,39 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Player", b => + modelBuilder.Entity("Server.Data.Entities.Player", b => { - b.HasOne("Server.Dal.Entities.Account", "Account") + b.HasOne("Server.Data.Entities.Account", "Account") .WithMany() .HasForeignKey("AccountId"); - b.HasOne("Server.Dal.Entities.Round", null) + b.HasOne("Server.Data.Entities.Round", null) .WithMany("Players") .HasForeignKey("RoundId"); b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Room", b => { - b.HasOne("Server.Dal.Entities.Player", "Player") + b.HasOne("Server.Data.Entities.Player", "Player") .WithMany() .HasForeignKey("PlayerId"); b.Navigation("Player"); }); - modelBuilder.Entity("Server.Dal.Entities.Round", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { - b.HasOne("Server.Dal.Entities.Account", "Loser") + b.HasOne("Server.Data.Entities.Account", "Loser") .WithMany() .HasForeignKey("LoserId"); - b.HasOne("Server.Dal.Entities.Room", "Room") + b.HasOne("Server.Data.Entities.Room", "Room") .WithOne("Round") - .HasForeignKey("Server.Dal.Entities.Round", "RoomId"); + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - b.HasOne("Server.Dal.Entities.Account", "Winner") + b.HasOne("Server.Data.Entities.Account", "Winner") .WithMany() .HasForeignKey("WinnerId"); @@ -200,26 +202,26 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Winner"); }); - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + modelBuilder.Entity("Server.Data.Entities.Statistics", b => { - b.HasOne("Server.Dal.Entities.Account", "Account") + b.HasOne("Server.Data.Entities.Account", "Account") .WithOne("Statistics") - .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId"); + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Data.Entities.Account", b => { b.Navigation("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Room", b => { b.Navigation("Round"); }); - modelBuilder.Entity("Server.Dal.Entities.Round", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { b.Navigation("Players"); }); diff --git a/Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs b/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.cs similarity index 100% rename from Server.Dal/Migrations/20220507161634_InitialMigrationStringKeys.cs rename to Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.cs diff --git a/Server.Dal/Migrations/ServerContextModelSnapshot.cs b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs similarity index 74% rename from Server.Dal/Migrations/ServerContextModelSnapshot.cs rename to Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs index 006773e..f7da844 100644 --- a/Server.Dal/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs @@ -2,22 +2,24 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Dal.Context; +using Server.Data.Context; #nullable disable namespace Server.Dal.Migrations { [DbContext(typeof(ServerContext))] - partial class ServerContextModelSnapshot : ModelSnapshot + [Migration("20220507201712_PlayersCollectionRoom")] + partial class PlayersCollectionRoom { - protected override void BuildModel(ModelBuilder modelBuilder) + protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Data.Entities.Account", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -33,7 +35,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Accounts"); }); - modelBuilder.Entity("Server.Dal.Entities.Player", b => + modelBuilder.Entity("Server.Data.Entities.Player", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -44,6 +46,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Move") .HasColumnType("INTEGER"); + b.Property("RoomId") + .HasColumnType("TEXT"); + b.Property("RoundId") .HasColumnType("TEXT"); @@ -51,12 +56,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("AccountId"); + b.HasIndex("RoomId"); + b.HasIndex("RoundId"); b.ToTable("Players"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Room", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -73,17 +80,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsPrivate") .HasColumnType("INTEGER"); - b.Property("PlayerId") - .HasColumnType("TEXT"); - b.HasKey("Id"); - b.HasIndex("PlayerId"); - b.ToTable("Rooms"); }); - modelBuilder.Entity("Server.Dal.Entities.Round", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -112,7 +114,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Rounds"); }); - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + modelBuilder.Entity("Server.Data.Entities.Statistics", b => { b.Property("Id") .HasColumnType("TEXT"); @@ -155,39 +157,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Player", b => + modelBuilder.Entity("Server.Data.Entities.Player", b => { - b.HasOne("Server.Dal.Entities.Account", "Account") + b.HasOne("Server.Data.Entities.Account", "Account") .WithMany() .HasForeignKey("AccountId"); - b.HasOne("Server.Dal.Entities.Round", null) + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Player") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) .WithMany("Players") .HasForeignKey("RoundId"); b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { - b.HasOne("Server.Dal.Entities.Player", "Player") - .WithMany() - .HasForeignKey("PlayerId"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Server.Dal.Entities.Round", b => - { - b.HasOne("Server.Dal.Entities.Account", "Loser") + b.HasOne("Server.Data.Entities.Account", "Loser") .WithMany() .HasForeignKey("LoserId"); - b.HasOne("Server.Dal.Entities.Room", "Room") + b.HasOne("Server.Data.Entities.Room", "Room") .WithOne("Round") - .HasForeignKey("Server.Dal.Entities.Round", "RoomId"); + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - b.HasOne("Server.Dal.Entities.Account", "Winner") + b.HasOne("Server.Data.Entities.Account", "Winner") .WithMany() .HasForeignKey("WinnerId"); @@ -198,26 +195,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Winner"); }); - modelBuilder.Entity("Server.Dal.Entities.Statistics", b => + modelBuilder.Entity("Server.Data.Entities.Statistics", b => { - b.HasOne("Server.Dal.Entities.Account", "Account") + b.HasOne("Server.Data.Entities.Account", "Account") .WithOne("Statistics") - .HasForeignKey("Server.Dal.Entities.Statistics", "AccountId"); + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); b.Navigation("Account"); }); - modelBuilder.Entity("Server.Dal.Entities.Account", b => + modelBuilder.Entity("Server.Data.Entities.Account", b => { b.Navigation("Statistics"); }); - modelBuilder.Entity("Server.Dal.Entities.Room", b => + modelBuilder.Entity("Server.Data.Entities.Room", b => { + b.Navigation("Player"); + b.Navigation("Round"); }); - modelBuilder.Entity("Server.Dal.Entities.Round", b => + modelBuilder.Entity("Server.Data.Entities.Round", b => { b.Navigation("Players"); }); diff --git a/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs new file mode 100644 index 0000000..5a0b95d --- /dev/null +++ b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class PlayersCollectionRoom : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rooms_Players_PlayerId", + table: "Rooms"); + + migrationBuilder.DropIndex( + name: "IX_Rooms_PlayerId", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "PlayerId", + table: "Rooms"); + + migrationBuilder.AddColumn( + name: "RoomId", + table: "Players", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Players_RoomId", + table: "Players", + column: "RoomId"); + + migrationBuilder.AddForeignKey( + name: "FK_Players_Rooms_RoomId", + table: "Players", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Players_Rooms_RoomId", + table: "Players"); + + migrationBuilder.DropIndex( + name: "IX_Players_RoomId", + table: "Players"); + + migrationBuilder.DropColumn( + name: "RoomId", + table: "Players"); + + migrationBuilder.AddColumn( + name: "PlayerId", + table: "Rooms", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_PlayerId", + table: "Rooms", + column: "PlayerId"); + + migrationBuilder.AddForeignKey( + name: "FK_Rooms_Players_PlayerId", + table: "Rooms", + column: "PlayerId", + principalTable: "Players", + principalColumn: "Id"); + } + } +} diff --git a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs new file mode 100644 index 0000000..840ad43 --- /dev/null +++ b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs @@ -0,0 +1,226 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220507201850_PlayersCollectionRoomFix")] + partial class PlayersCollectionRoomFix + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs new file mode 100644 index 0000000..3fa172f --- /dev/null +++ b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class PlayersCollectionRoomFix : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs new file mode 100644 index 0000000..581aec2 --- /dev/null +++ b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs @@ -0,0 +1,229 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220507202003_PlayerReadyFlag")] + partial class PlayerReadyFlag + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs new file mode 100644 index 0000000..71d2c9a --- /dev/null +++ b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class PlayerReadyFlag : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsReady", + table: "Players", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsReady", + table: "Players"); + } + } +} diff --git a/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs b/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs new file mode 100644 index 0000000..656245d --- /dev/null +++ b/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220507205137_DateEntities")] + partial class DateEntities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FinishTime") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Data/Migrations/20220507205137_DateEntities.cs b/Server.Data/Migrations/20220507205137_DateEntities.cs new file mode 100644 index 0000000..1b9211c --- /dev/null +++ b/Server.Data/Migrations/20220507205137_DateEntities.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class DateEntities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FinishTime", + table: "Rounds", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "StartTime", + table: "Rounds", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FinishTime", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "StartTime", + table: "Rounds"); + } + } +} diff --git a/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server.Data/Migrations/ServerContextModelSnapshot.cs new file mode 100644 index 0000000..77b1b76 --- /dev/null +++ b/Server.Data/Migrations/ServerContextModelSnapshot.cs @@ -0,0 +1,233 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + partial class ServerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FinishTime") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Dal/Server.Dal.csproj b/Server.Data/Server.Data.csproj similarity index 100% rename from Server.Dal/Server.Dal.csproj rename to Server.Data/Server.Data.csproj diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 8e4743b..32e11e8 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -25,7 +25,7 @@ - + diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index bd209b5..d3fcbd7 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -5,8 +5,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Server.Authentication.Extensions; -using Server.Dal.Context; -using Server.Dal.Extensions; +using Server.Data.Context; +using Server.Data.Extensions; using Server.Host.Extensions; namespace Server.Host; From 0d8af08a778051698574ccec36df6674de9f72cd Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 8 May 2022 00:29:08 +0300 Subject: [PATCH 38/56] Added documentation for completed Server.Authentication.csproj module. --- Server.Authentication/AuthOptions.cs | 16 ++++---- .../Exceptions/UserExceptionsTemplates.cs | 41 ++++++++++++++++--- .../Extensions/AuthenticationExtension.cs | 2 +- Server.Authentication/HashingBase64.cs | 28 ++++++++++++- .../Models/ApplicationUser.cs | 11 +++-- Server.Authentication/Models/AuthUser.cs | 35 ---------------- .../Models/Interfaces/IApplicationUser.cs | 4 +- .../Models/Interfaces/IAuthUser.cs | 12 ------ Server.Authentication/Models/Roles.cs | 6 +-- .../Server.Authentication.csproj | 2 + .../Services/AttemptValidationService.cs | 5 ++- Server.Authentication/Services/AuthService.cs | 16 ++++---- .../Services/IAuthService.cs | 27 ++++++++++++ 13 files changed, 121 insertions(+), 84 deletions(-) delete mode 100644 Server.Authentication/Models/AuthUser.cs delete mode 100644 Server.Authentication/Models/Interfaces/IAuthUser.cs diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index 5c9fa4f..8d4849e 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -5,39 +5,39 @@ namespace Server.Authentication; /// -/// Options for JWT token +/// Options for JWT token.. /// public sealed class AuthOptions { /// - /// Token issuer (producer). + /// Token issuer (producer). /// public string Issuer { get; set; } = "Rock Paper Scissors"; /// - /// Token audience (consumer). + /// Token audience (consumer). /// public string Audience { get; set; } = "Player"; /// - /// Token secret part. + /// Token secret part. /// public string PrivateKey { get; set; } = "RockPaperScissors"; /// - /// Token life time. + /// Token life time. /// public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3); /// - /// Require HTTPS. + /// Require HTTPS. /// public bool RequireHttps { get; set; } = false; /// - /// Getting a symmetric security key + /// Getting a symmetric security key. /// - /// + /// . public SymmetricSecurityKey GetSymmetricSecurityKey() { return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey)); diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs index ca99e76..26b2525 100644 --- a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs +++ b/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -2,17 +2,46 @@ namespace Server.Authentication.Exceptions; +/// +/// Compile-time exception templates for Authentication module. +/// internal static class UserExceptionsTemplates { - internal static string UserCoolDown(this string userName, DateTimeOffset date) => - $"User [{userName}] has been cooled down till \"{date}\"."; + /// + /// Builds exception template for Cooldown of Client login attempts. + /// + /// Client username. + /// Date of removing cooldown. + /// Created string message. + internal static string UserCoolDown(this string username, DateTimeOffset tillCooldownDate) + => $"User [{username}] has been cooled down till \"{tillCooldownDate}\"."; - internal static string UserInvalidCredentials(this string userCredentials) => - $"Invalid Credentials for user \"{userCredentials}\"."; + /// + /// Builds exception template for invalid credentials for user login attempt. + /// + /// Client input username. + /// Created string message. + internal static string UserInvalidCredentials(this string username) + => $"Invalid Credentials for user \"{username}\"."; - internal static string UserNotFound(this string userName) => $"User with login \"{userName}\" is not found."; + /// + /// Builds exception template for not found client by input parameters. + /// + /// Client input username. + /// Created string message. + internal static string UserNotFound(this string username) + => $"User with login \"{username}\" is not found."; - internal static string UserAlreadyExists(this string userName) => $"User with login \"{userName}\" already exists."; + /// + /// Builds exception template for registering client, that already exists. + /// + /// Client input username. + /// Created string message. + internal static string UserAlreadyExists(this string username) + => $"User with login \"{username}\" already exists."; + /// + /// Default template, when error is unknown. + /// internal const string UnknownError = "Unknown error occured. Please try again."; } \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index 80cbbce..19684a9 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -17,7 +17,7 @@ public static class AuthenticationExtension /// /// Registers authentication. /// - /// Service collection + /// Service collection. public static IServiceCollection AddAuthentications(this IServiceCollection services) { _ = services ?? throw new ArgumentNullException(nameof(services)); diff --git a/Server.Authentication/HashingBase64.cs b/Server.Authentication/HashingBase64.cs index dae0792..e9bec0d 100644 --- a/Server.Authentication/HashingBase64.cs +++ b/Server.Authentication/HashingBase64.cs @@ -2,8 +2,16 @@ namespace Server.Authentication; +/// +/// Default hashing base64 methods. +/// public static class HashingBase64 { + /// + /// Decodes input string from BASE64 to a regular string. + /// + /// Encoded in BASE64 payload. + /// Decoded string. public static string DecodeBase64(this string encodedData) { var encodedDataAsBytes @@ -12,13 +20,29 @@ var encodedDataAsBytes System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); } - public static string EncodeBase64(this string decodedData) + /// + /// Encodes input string to BASE64 From a regular string. + /// + /// Payload to be encoded. + /// Encoded string. + public static string EncodeBase64(this string initialData) { - var dataArray = System.Text.Encoding.ASCII.GetBytes(decodedData); + var dataArray = System.Text.Encoding.ASCII.GetBytes(initialData); return Convert.ToBase64String(dataArray); } + /// + /// Compares initial encoded data with base64 data. + /// + /// Encoded data. + /// Initial payload. + /// + /// + /// true - when initial data, encoded in base64 is identical with base64 data. + /// + /// false - otherwise. + /// public static bool IsHashEqual(this string base64Data, string initialData) { var baseDecoded = EncodeBase64(initialData); diff --git a/Server.Authentication/Models/ApplicationUser.cs b/Server.Authentication/Models/ApplicationUser.cs index 983eb9c..b6348bd 100644 --- a/Server.Authentication/Models/ApplicationUser.cs +++ b/Server.Authentication/Models/ApplicationUser.cs @@ -1,19 +1,18 @@ using System; -using System.Linq; using Microsoft.AspNetCore.Http; using Server.Authentication.Models.Interfaces; namespace Server.Authentication.Models; /// -/// Application user +/// Application user. /// internal sealed class ApplicationUser: IApplicationUser { private readonly IHttpContextAccessor _httpContextAccessor; /// - /// Constructor + /// Constructor. /// /// . public ApplicationUser(IHttpContextAccessor httpContextAccessor) @@ -22,15 +21,15 @@ public ApplicationUser(IHttpContextAccessor httpContextAccessor) } /// - /// This user id + /// Retrieved user id from JWT token. /// public string Id => GetUserId(); private string GetUserId() { var request = _httpContextAccessor.HttpContext - ?.User.Identity.Name; + ?.User.Identity?.Name; - return request; + return request ?? throw new ArgumentNullException(nameof(request)); } } \ No newline at end of file diff --git a/Server.Authentication/Models/AuthUser.cs b/Server.Authentication/Models/AuthUser.cs deleted file mode 100644 index 45dbcaa..0000000 --- a/Server.Authentication/Models/AuthUser.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using Microsoft.AspNetCore.Http; -using Server.Authentication.Models.Interfaces; - -namespace Server.Authentication.Models; - -/// -/// Application user -/// -internal sealed class AuthUser: IAuthUser -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - /// - /// Constructor - /// - /// http accessor - public AuthUser(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - /// - /// This user шв - /// - public int Id => GetUserId(); - - private int GetUserId() - { - var request = _httpContextAccessor.HttpContext - ?.User.Claims.FirstOrDefault(x => x.Type == "id"); - - return int.TryParse(request?.Value, out var id) ? id : 0; - } -} \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs index f34ce2d..4ef8c45 100644 --- a/Server.Authentication/Models/Interfaces/IApplicationUser.cs +++ b/Server.Authentication/Models/Interfaces/IApplicationUser.cs @@ -1,12 +1,12 @@ namespace Server.Authentication.Models.Interfaces; /// -/// Interface for ApplicationUser class +/// Interface for ApplicationUser class. /// public interface IApplicationUser { /// - /// Gets user request id from token + /// Gets user request id from token. /// string Id { get; } } \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IAuthUser.cs b/Server.Authentication/Models/Interfaces/IAuthUser.cs deleted file mode 100644 index 53ed3b8..0000000 --- a/Server.Authentication/Models/Interfaces/IAuthUser.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Server.Authentication.Models.Interfaces; - -/// -/// Interface for ApplicationUser class. -/// -public interface IAuthUser -{ - /// - /// Gets user request id from token. - /// - int Id { get; } -} \ No newline at end of file diff --git a/Server.Authentication/Models/Roles.cs b/Server.Authentication/Models/Roles.cs index 7746b64..95879b6 100644 --- a/Server.Authentication/Models/Roles.cs +++ b/Server.Authentication/Models/Roles.cs @@ -1,17 +1,17 @@ namespace Server.Authentication.Models; /// -/// User roles +/// User roles. /// public static class Roles { /// - /// Admin role + /// Admin role. /// public const string Admin = "admin"; /// - /// User role + /// User role. /// public const string User = "user"; } \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index de25ef1..74970c4 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -1,7 +1,9 @@ + true net6 + enable diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 6409596..d27a707 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -3,9 +3,12 @@ namespace Server.Authentication.Services; +/// +/// Static service with validation features. +/// internal static class AttemptValidationService { - // key - userId. Value - failed attempts + // key - userId. Value - count of failed attempts private static readonly ConcurrentDictionary FailedAttempts = new(); private static readonly ConcurrentDictionary CoolDownCollection = new(); diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index cd835ec..ab1be6b 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -17,10 +17,11 @@ namespace Server.Authentication.Services; +/// internal sealed class AuthService : IAuthService { private static readonly JwtSecurityTokenHandler TokenHandler = new(); - private static SigningCredentials _signingCredentials; + private static SigningCredentials _signingCredentials = null!; private readonly ServerContext _repository; private readonly AuthOptions _authOptions; @@ -28,6 +29,12 @@ internal sealed class AuthService : IAuthService private readonly SemaphoreSlim _semaphore = new(1, 1); + /// + /// Constructor. + /// + /// . + /// . + /// . public AuthService( ILogger logger, IOptions authOptions, @@ -129,13 +136,6 @@ public async Task> LoginAsync(string lo return new UserException(login.UserInvalidCredentials()); } - public Task RemoveAsync(string accountId) - { - _repository.Accounts.Remove(new Account { Id = accountId }); - - return _repository.SaveChangesAsync(); - } - private string BuildToken(Account accountModel) { var now = DateTime.UtcNow; diff --git a/Server.Authentication/Services/IAuthService.cs b/Server.Authentication/Services/IAuthService.cs index 8f2fa17..4c67505 100644 --- a/Server.Authentication/Services/IAuthService.cs +++ b/Server.Authentication/Services/IAuthService.cs @@ -1,13 +1,40 @@ using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using OneOf; using Server.Authentication.Exceptions; using Server.Authentication.Models; +using Server.Data.Entities; namespace Server.Authentication.Services; +/// +/// Authentication service. +/// public interface IAuthService { + /// + /// Registers new entity of Client. (). + /// + /// Client login, case insensitive. + /// Client password. + /// + /// + /// int - if everything is fine. + /// + /// UserException - If some case of error occured. (User exists, validation error, unknown error). + /// Task> RegisterAsync(string login, string password); + /// + /// Signs in client by credentials and building JWT token. + /// + /// Client login, case insensitive. + /// Client password. + /// + /// + /// AccountOutputModel - Constructed object with login and JWT token. + /// + /// UserException - If some case of error occured. (User exists, validation error, unknown error). + /// Task> LoginAsync(string login, string password); } \ No newline at end of file From 85102fe5b7b95ca63ec12b10d55acfab6ae09e6a Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 8 May 2022 12:50:14 +0300 Subject: [PATCH 39/56] Some more tweaks --- .../Services/Interfaces/ILongPollingService.cs | 5 +++-- Server.Bll/Services/LongPollingService.cs | 13 ++++++------- Server.Host/Controllers/AccountController.cs | 2 +- Server.Host/Controllers/LongPollingController.cs | 9 +++++++-- Server.Host/Controllers/RoomController.cs | 4 +++- Server.Host/Controllers/StatisticsController.cs | 9 +++++---- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server.Bll/Services/Interfaces/ILongPollingService.cs index 01b4589..460f2ab 100644 --- a/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ b/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -4,6 +4,7 @@ namespace Server.Bll.Services.Interfaces; public interface ILongPollingService { - Task CheckRoomState(int roomId); - Task CheckRoundState(int roundId); + Task CheckRoomState(string roomId); + + Task CheckRoundState(string roundId); } \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index dd6241d..d6a0565 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Server.Bll.Services.Interfaces; using Server.Data.Context; @@ -13,17 +14,15 @@ public LongPollingService(ServerContext serverContext) _serverContext = serverContext; } - public async Task CheckRoomState(int roomId) + public Task CheckRoomState(string roomId) { - var thisRoom = await _serverContext.Rooms.FindAsync(roomId.ToString()); - - return thisRoom is not null; + return _serverContext.Rooms.AnyAsync(room => room.Id.Equals(roomId)); } - public async Task CheckRoundState(int roundId) + public async Task CheckRoundState(string roundId) { - var thisRound = await _serverContext.Rounds.FindAsync(roundId.ToString()); + var thisRound = await _serverContext.Rounds.FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); - return thisRound.IsFinished; + return thisRound?.IsFinished ?? false; } } \ No newline at end of file diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index d6c5d5b..27906d5 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -33,7 +33,7 @@ public async Task RegisterAsync(RegisterRequest registerRequest) .RegisterAsync(registerRequest.Login,registerRequest.Password); return newAccount.Match( - statusCode => Ok(statusCode), + statusCode => Ok(statusCode.ToString()), userException => BadRequest(userException)); } diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index 4ae2f15..2a82876 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -1,8 +1,11 @@ using System; +using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Server.Authentication.Exceptions; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; @@ -20,13 +23,15 @@ public LongPollingController(ILongPollingService longPollingService) } [HttpGet("room")] - public Task CheckRoomState([FromQuery] int roomId) + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + public Task CheckRoomState([FromQuery] string roomId) { return _longPollingService.CheckRoomState(roomId); } [HttpGet("round")] - public Task CheckRoundState(int roundId) + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + public Task CheckRoundState(string roundId) { return _longPollingService.CheckRoundState(roundId); } diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 5ba0de1..9b76651 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -33,7 +33,9 @@ public RoomController( [HttpPost("create")] //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] - public async Task CreateRoom([FromQuery] bool isPrivate, + + public async Task CreateRoom( + [FromQuery] bool isPrivate, [FromHeader(Name="X-Training")] bool isTraining = false) { throw new NotImplementedException(); diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index b55a6a0..b6900e8 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Server.Authentication.Models.Interfaces; using Server.Bll.Exceptions; @@ -34,8 +35,8 @@ public StatisticsController(IStatisticsService statisticsService, IApplicationUs [AllowAnonymous] [HttpGet("all")] - [ProducesResponseType(typeof(ShortStatisticsModel[]), (int) HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ShortStatisticsModel[]), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public Task GetOverallStatistics() { return _statisticsService.GetAllStatistics(); @@ -43,8 +44,8 @@ public Task GetOverallStatistics() [Authorize] [HttpGet("personal")] - [ProducesResponseType(typeof(StatisticsModel), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(CustomException), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(StatisticsModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(CustomException), StatusCodes.Status400BadRequest)] public async Task GetPersonalStatistics() { var result = await _statisticsService.GetPersonalStatistics(UserId); From 79c0a4ee03491ab45a5d80a987fd45f985cab82f Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 8 May 2022 12:59:52 +0300 Subject: [PATCH 40/56] Fixed roomController --- .../Services/Interfaces/IRoomService.cs | 2 +- Server.Bll/Services/RoomService.cs | 2 +- Server.Host/Controllers/RoomController.cs | 96 +++++++++---------- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 05c4b3d..771d8e1 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -12,7 +12,7 @@ public interface IRoomService Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate); - Task> JoinAsync(string userId, bool isPrivate, string roomCode); + Task> JoinAsync(string userId, bool isPrivate, string? roomCode = default); Task> GetAsync(string roomId); diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 0f552b7..fbac243 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -229,7 +229,7 @@ public async Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutD return rooms.Length + allRounds.Length; } - public Task> JoinAsync(string userId, bool isPrivate, string roomCode) + public Task> JoinAsync(string userId, bool isPrivate, string? roomCode = default) { throw new NotImplementedException(); } diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 9b76651..ec51907 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -1,9 +1,9 @@ using System; -using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Server.Authentication.Models.Interfaces; using Server.Bll.Models; @@ -20,7 +20,6 @@ public sealed class RoomController : ControllerBase { private readonly IRoomService _roomService; private readonly IApplicationUser _applicationUser; - private string UserId => _applicationUser.Id; public RoomController( IRoomService roomService, @@ -30,70 +29,71 @@ public RoomController( _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } - [HttpPost("create")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int) HttpStatusCode.BadRequest)] + private string UserId => _applicationUser.Id; - public async Task CreateRoom( + [HttpPost("create")] + [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateAsync( [FromQuery] bool isPrivate, [FromHeader(Name="X-Training")] bool isTraining = false) { - throw new NotImplementedException(); - // var newRoom = await _roomService - // .CreateAsync(UserId, isPrivate, isTraining); - // - // return newRoom.Match( - // Ok, - // exception => BadRequest(exception)); + var newRoom = await _roomService + .CreateAsync(UserId, isPrivate, isTraining); + + return newRoom.Match( + roomModel => Ok(roomModel), + exception => BadRequest(exception)); } [HttpPost("join/public")] - public async Task JoinPublicRoom() + [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task JoinPublicAsync() { - throw new NotImplementedException(); - // var result = await _roomService.JoinAsync(UserId, true,null); - // return result.Match( - // Ok, - // exception => BadRequest(exception)); + var result = await _roomService.JoinAsync(UserId, true); + + return result.Match( + roomModel => Ok(roomModel), + exception => BadRequest(exception)); } [HttpPost("join/private")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task JoinPrivateRoom([FromQuery] string roomCode) + [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task JoinPrivateAsync([FromQuery] string roomCode) { - throw new NotImplementedException(); - // var result = await _roomService.JoinAsync(UserId, false, roomCode); - // return result.Match( - // Ok, - // exception => BadRequest(exception)); + var result = await _roomService.JoinAsync(UserId, false, roomCode); + + return result.Match( + roomModel => Ok(roomModel), + exception => BadRequest(exception)); } [HttpGet("update")] - //[ProducesResponseType(typeof(Room), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateRoom([FromBody] RoomModel roomModel) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task UpdateAsync([FromBody] RoomModel roomModel) { - throw new NotImplementedException(); - // var updateResponse = await _roomService.UpdateRoom(roomModel); - // - // return updateResponse switch - // { - // 200 => Ok(), - // _ => BadRequest() - // }; + var updateResponse = await _roomService.UpdateAsync(roomModel); + + return updateResponse switch + { + StatusCodes.Status200OK => Ok(), + _ => BadRequest() + }; } [HttpDelete("delete")] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task DeleteRoom([FromQuery] int roomId) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task DeleteAsync([FromQuery] string roomId) { - throw new NotImplementedException(); - // var deleteResponse = await _roomService.DeleteAsync(UserId,roomId); - // - // return deleteResponse switch - // { - // 200 => Ok(), - // _ => BadRequest() - // }; + var deleteResponse = await _roomService.DeleteAsync(UserId, roomId); + + return deleteResponse switch + { + StatusCodes.Status200OK => Ok(), + _ => BadRequest() + }; } } \ No newline at end of file From 298e574d897d54347ec876c9650cd8bd0e8c1b92 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 8 May 2022 17:13:07 +0300 Subject: [PATCH 41/56] Refactoring of controllers --- .../Extensions/AuthenticationExtension.cs | 5 +-- .../Models/ApplicationUser.cs | 35 ------------------- .../Models/Interfaces/IApplicationUser.cs | 12 ------- Server.Authentication/Services/AuthService.cs | 1 - Server.Bll/Services/LongPollingService.cs | 3 +- Server.Data/Context/ServerContext.cs | 4 +++ Server.Data/Extensions/DatabaseExtension.cs | 2 ++ Server.Host/Controllers/RoomController.cs | 9 ++--- Server.Host/Controllers/RoundController.cs | 14 ++++---- .../Controllers/StatisticsController.cs | 14 +++----- 10 files changed, 21 insertions(+), 78 deletions(-) delete mode 100644 Server.Authentication/Models/ApplicationUser.cs delete mode 100644 Server.Authentication/Models/Interfaces/IApplicationUser.cs diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index 19684a9..c7d8f51 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -3,8 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; -using Server.Authentication.Models; -using Server.Authentication.Models.Interfaces; using Server.Authentication.Services; namespace Server.Authentication.Extensions; @@ -49,8 +47,7 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv }); services - .AddTransient() - .AddTransient(); + .AddTransient(); return services; } diff --git a/Server.Authentication/Models/ApplicationUser.cs b/Server.Authentication/Models/ApplicationUser.cs deleted file mode 100644 index b6348bd..0000000 --- a/Server.Authentication/Models/ApplicationUser.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Server.Authentication.Models.Interfaces; - -namespace Server.Authentication.Models; - -/// -/// Application user. -/// -internal sealed class ApplicationUser: IApplicationUser -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - /// - /// Constructor. - /// - /// . - public ApplicationUser(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - } - - /// - /// Retrieved user id from JWT token. - /// - public string Id => GetUserId(); - - private string GetUserId() - { - var request = _httpContextAccessor.HttpContext - ?.User.Identity?.Name; - - return request ?? throw new ArgumentNullException(nameof(request)); - } -} \ No newline at end of file diff --git a/Server.Authentication/Models/Interfaces/IApplicationUser.cs b/Server.Authentication/Models/Interfaces/IApplicationUser.cs deleted file mode 100644 index 4ef8c45..0000000 --- a/Server.Authentication/Models/Interfaces/IApplicationUser.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Server.Authentication.Models.Interfaces; - -/// -/// Interface for ApplicationUser class. -/// -public interface IApplicationUser -{ - /// - /// Gets user request id from token. - /// - string Id { get; } -} \ No newline at end of file diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index ab1be6b..855ebe8 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -26,7 +26,6 @@ internal sealed class AuthService : IAuthService private readonly ServerContext _repository; private readonly AuthOptions _authOptions; private readonly ILogger _logger; - private readonly SemaphoreSlim _semaphore = new(1, 1); /// diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index d6a0565..3f922f9 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Server.Bll.Services.Interfaces; @@ -11,7 +12,7 @@ internal sealed class LongPollingService : ILongPollingService public LongPollingService(ServerContext serverContext) { - _serverContext = serverContext; + _serverContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); } public Task CheckRoomState(string roomId) diff --git a/Server.Data/Context/ServerContext.cs b/Server.Data/Context/ServerContext.cs index 7b907f9..1a09ffa 100644 --- a/Server.Data/Context/ServerContext.cs +++ b/Server.Data/Context/ServerContext.cs @@ -6,10 +6,14 @@ namespace Server.Data.Context; public sealed class ServerContext : DbContext { public DbSet Accounts { get; set; } + public DbSet Rooms { get; set; } + public DbSet Players { get; set; } + public DbSet Rounds { get; set; } + public DbSet StatisticsEnumerable { get; set; } public ServerContext(DbContextOptions contextOptions) diff --git a/Server.Data/Extensions/DatabaseExtension.cs b/Server.Data/Extensions/DatabaseExtension.cs index 300e461..9ab6d24 100644 --- a/Server.Data/Extensions/DatabaseExtension.cs +++ b/Server.Data/Extensions/DatabaseExtension.cs @@ -8,7 +8,9 @@ namespace Server.Data.Extensions; public static class DatabaseExtension { private const string DatabaseConnection = "DatabaseConnection"; + private const string MigrationAssemblyName = "Server.Data"; + public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { return service.AddDbContext( diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index ec51907..400584b 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Server.Authentication.Models.Interfaces; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -19,17 +18,13 @@ namespace Server.Host.Controllers; public sealed class RoomController : ControllerBase { private readonly IRoomService _roomService; - private readonly IApplicationUser _applicationUser; - public RoomController( - IRoomService roomService, - IApplicationUser applicationUser) + public RoomController(IRoomService roomService) { _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService)); - _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } - private string UserId => _applicationUser.Id; + private string UserId => User.Identity?.Name ?? string.Empty; [HttpPost("create")] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 08cb892..15fa16f 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Server.Authentication.Models.Interfaces; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -20,16 +19,15 @@ namespace Server.Host.Controllers; [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoundController:ControllerBase { - private readonly IApplicationUser _applicationUser; private readonly IRoundService _roundService; - private string UserId => _applicationUser.Id; - public RoundController( - IRoundService roundService, - IApplicationUser applicationUser) + + public RoundController(IRoundService roundService) { - _roundService = roundService; - _applicationUser = applicationUser; + _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); } + + private string UserId => User.Identity?.Name ?? string.Empty; + /// /// Creates round in room /// diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index b6900e8..5c6d236 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -1,17 +1,13 @@ using System; -using System.Collections.Generic; -using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Server.Authentication.Models.Interfaces; using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using Server.Host.Contracts; namespace Server.Host.Controllers; @@ -23,16 +19,14 @@ namespace Server.Host.Controllers; public sealed class StatisticsController : ControllerBase { private readonly IStatisticsService _statisticsService; - private readonly IApplicationUser _applicationUser; - - private string UserId => _applicationUser.Id; - - public StatisticsController(IStatisticsService statisticsService, IApplicationUser applicationUser) + + public StatisticsController(IStatisticsService statisticsService) { _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); - _applicationUser = applicationUser ?? throw new ArgumentNullException(nameof(applicationUser)); } + private string UserId => User.Identity?.Name ?? string.Empty; + [AllowAnonymous] [HttpGet("all")] [ProducesResponseType(typeof(ShortStatisticsModel[]), StatusCodes.Status200OK)] From 2f076d155959979ce4da78bf4399c45860447b8e Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 14 May 2022 16:00:49 +0300 Subject: [PATCH 42/56] Added new Properties --- .../HostedServices/CleanerHostedService.cs | 6 +- Server.Bll/Services/RoomService.cs | 32 ++- Server.Bll/Services/RoundService.cs | 2 +- Server.Data/Entities/Round.cs | 4 +- Server.Data/Extensions/DatabaseExtension.cs | 9 +- ...20220508143835_DateEntitiesFix.Designer.cs | 235 ++++++++++++++++++ .../20220508143835_DateEntitiesFix.cs | 19 ++ ...0508144103_DateTimeToLongTicks.Designer.cs | 235 ++++++++++++++++++ .../20220508144103_DateTimeToLongTicks.cs | 60 +++++ .../Migrations/ServerContextModelSnapshot.cs | 8 +- Server.Data/Server.Data.csproj | 1 - .../Controllers/LongPollingController.cs | 5 +- Server.Host/Extensions/LoggingMiddleware.cs | 101 +++++++- Server.Host/Extensions/ServiceExtension.cs | 14 -- Server.Host/Startup.cs | 9 +- 15 files changed, 685 insertions(+), 55 deletions(-) create mode 100644 Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs create mode 100644 Server.Data/Migrations/20220508143835_DateEntitiesFix.cs create mode 100644 Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs create mode 100644 Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs delete mode 100644 Server.Host/Extensions/ServiceExtension.cs diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server.Bll/HostedServices/CleanerHostedService.cs index 020d46f..645398c 100644 --- a/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server.Bll/HostedServices/CleanerHostedService.cs @@ -12,7 +12,7 @@ public sealed class CleanerHostedService : IHostedService { private readonly IServiceScopeFactory _serviceProvider; private readonly ILogger _logger; - private Timer _timer; + private Timer? _timer; // todo: options of max time @@ -37,9 +37,9 @@ public Task StartAsync(CancellationToken cancellationToken) return Task.CompletedTask; } - private async void CleanJunk(object state) + private async void CleanJunk(object? state) { - var factory = (IServiceScopeFactory) state; + var factory = (IServiceScopeFactory) state!; using var scope = factory.CreateScope(); var roomService = scope.ServiceProvider.GetRequiredService(); //todo: timespan to option. diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index fbac243..05e6c4c 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -210,23 +210,37 @@ public Task> GetAsync(string roomId) public async Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate) { - var currentDate = DateTimeOffset.Now.Ticks; + var currentDate = DateTimeOffset.UtcNow.Ticks; var rooms = await _repository.Rooms .Include(room => room.Round) .Where(room => room.CreationTimeTicks + roomOutDate.Ticks < currentDate && room.Round == null) .ToArrayAsync(); - + + var roomLength = rooms.Length; + var allRounds = await _repository.Rounds - .Where(round => round.FinishTime.Ticks + roundOutDate.Ticks < currentDate) + .Where(round => round.FinishTimeTicks + roundOutDate.Ticks < currentDate) .ToArrayAsync(); - - _repository.Rooms.RemoveRange(rooms); - _repository.Rounds.RemoveRange(allRounds); - - await _repository.SaveChangesAsync(); + + var roundsLength = allRounds.Length; - return rooms.Length + allRounds.Length; + if (roomLength is not 0) + { + _repository.Rooms.RemoveRange(rooms); + } + + if (roundsLength is not 0) + { + _repository.Rounds.RemoveRange(allRounds); + } + + if (roundsLength is not 0 && roomLength is not 0) + { + await _repository.SaveChangesAsync(); + } + + return roomLength + roundsLength; } public Task> JoinAsync(string userId, bool isPrivate, string? roomCode = default) diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index 3cdf28c..0885c5f 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -48,7 +48,7 @@ public async Task> CreateAsync(string userId, Id = Guid.NewGuid().ToString(), RoomId = roomId, Room = playingRoom, - StartTime = DateTimeOffset.UtcNow, + StartTimeTicks = DateTimeOffset.UtcNow.Ticks, IsFinished = false }; diff --git a/Server.Data/Entities/Round.cs b/Server.Data/Entities/Round.cs index 2c5663a..0d1b9eb 100644 --- a/Server.Data/Entities/Round.cs +++ b/Server.Data/Entities/Round.cs @@ -23,9 +23,9 @@ public class Round public virtual Account Loser { get; set; } - public DateTimeOffset StartTime { get; set; } + public long StartTimeTicks { get; set; } - public DateTimeOffset FinishTime { get; set; } + public long FinishTimeTicks { get; set; } public bool IsFinished { get; set; } } \ No newline at end of file diff --git a/Server.Data/Extensions/DatabaseExtension.cs b/Server.Data/Extensions/DatabaseExtension.cs index 9ab6d24..9f8f3b1 100644 --- a/Server.Data/Extensions/DatabaseExtension.cs +++ b/Server.Data/Extensions/DatabaseExtension.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System.Reflection; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Server.Data.Context; @@ -8,15 +9,13 @@ namespace Server.Data.Extensions; public static class DatabaseExtension { private const string DatabaseConnection = "DatabaseConnection"; - - private const string MigrationAssemblyName = "Server.Data"; - + public static IServiceCollection AddDatabase(this IServiceCollection service, IConfiguration configuration) { return service.AddDbContext( builder => builder.UseSqlite (configuration.GetConnectionString(DatabaseConnection), - optionsBuilder => optionsBuilder.MigrationsAssembly(MigrationAssemblyName)), + optionsBuilder => optionsBuilder.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)), ServiceLifetime.Transient); } } \ No newline at end of file diff --git a/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs b/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs new file mode 100644 index 0000000..93bbbbd --- /dev/null +++ b/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220508143835_DateEntitiesFix")] + partial class DateEntitiesFix + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FinishTime") + .HasColumnType("TEXT"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs b/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs new file mode 100644 index 0000000..dcdc29f --- /dev/null +++ b/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class DateEntitiesFix : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs b/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs new file mode 100644 index 0000000..22c95f3 --- /dev/null +++ b/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Server.Data.Context; + +#nullable disable + +namespace Server.Dal.Migrations +{ + [DbContext(typeof(ServerContext))] + [Migration("20220508144103_DateTimeToLongTicks")] + partial class DateTimeToLongTicks + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Login") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("IsReady") + .HasColumnType("INTEGER"); + + b.Property("Move") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoundId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("CreationTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFull") + .HasColumnType("INTEGER"); + + b.Property("IsPrivate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FinishTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("IsFinished") + .HasColumnType("INTEGER"); + + b.Property("LoserId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("TEXT"); + + b.Property("StartTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("WinnerId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LoserId"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.HasIndex("WinnerId"); + + b.ToTable("Rounds"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .HasColumnType("TEXT"); + + b.Property("Draws") + .HasColumnType("INTEGER"); + + b.Property("Loss") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("TimeSpent") + .HasColumnType("TEXT"); + + b.Property("UsedPaper") + .HasColumnType("INTEGER"); + + b.Property("UsedRock") + .HasColumnType("INTEGER"); + + b.Property("UsedScissors") + .HasColumnType("INTEGER"); + + b.Property("WinLossRatio") + .HasColumnType("REAL"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Player", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.HasOne("Server.Data.Entities.Room", null) + .WithMany("Players") + .HasForeignKey("RoomId"); + + b.HasOne("Server.Data.Entities.Round", null) + .WithMany("Players") + .HasForeignKey("RoundId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.HasOne("Server.Data.Entities.Account", "Loser") + .WithMany() + .HasForeignKey("LoserId"); + + b.HasOne("Server.Data.Entities.Room", "Room") + .WithOne("Round") + .HasForeignKey("Server.Data.Entities.Round", "RoomId"); + + b.HasOne("Server.Data.Entities.Account", "Winner") + .WithMany() + .HasForeignKey("WinnerId"); + + b.Navigation("Loser"); + + b.Navigation("Room"); + + b.Navigation("Winner"); + }); + + modelBuilder.Entity("Server.Data.Entities.Statistics", b => + { + b.HasOne("Server.Data.Entities.Account", "Account") + .WithOne("Statistics") + .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.Data.Entities.Account", b => + { + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("Server.Data.Entities.Room", b => + { + b.Navigation("Players"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Server.Data.Entities.Round", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs b/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs new file mode 100644 index 0000000..0d55c41 --- /dev/null +++ b/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Dal.Migrations +{ + public partial class DateTimeToLongTicks : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FinishTime", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "StartTime", + table: "Rounds"); + + migrationBuilder.AddColumn( + name: "FinishTimeTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "StartTimeTicks", + table: "Rounds", + type: "INTEGER", + nullable: false, + defaultValue: 0L); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FinishTimeTicks", + table: "Rounds"); + + migrationBuilder.DropColumn( + name: "StartTimeTicks", + table: "Rounds"); + + migrationBuilder.AddColumn( + name: "FinishTime", + table: "Rounds", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "StartTime", + table: "Rounds", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + } +} diff --git a/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server.Data/Migrations/ServerContextModelSnapshot.cs index 77b1b76..6fc5db1 100644 --- a/Server.Data/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Data/Migrations/ServerContextModelSnapshot.cs @@ -91,8 +91,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("TEXT"); - b.Property("FinishTime") - .HasColumnType("TEXT"); + b.Property("FinishTimeTicks") + .HasColumnType("INTEGER"); b.Property("IsFinished") .HasColumnType("INTEGER"); @@ -103,8 +103,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RoomId") .HasColumnType("TEXT"); - b.Property("StartTime") - .HasColumnType("TEXT"); + b.Property("StartTimeTicks") + .HasColumnType("INTEGER"); b.Property("WinnerId") .HasColumnType("TEXT"); diff --git a/Server.Data/Server.Data.csproj b/Server.Data/Server.Data.csproj index 5cce87f..415d72f 100644 --- a/Server.Data/Server.Data.csproj +++ b/Server.Data/Server.Data.csproj @@ -5,7 +5,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index 2a82876..d6a8b20 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Server.Authentication.Exceptions; using Server.Bll.Services.Interfaces; @@ -16,10 +17,12 @@ namespace Server.Host.Controllers; public sealed class LongPollingController : ControllerBase { private readonly ILongPollingService _longPollingService; + private readonly ILogger _logger; - public LongPollingController(ILongPollingService longPollingService) + public LongPollingController(ILongPollingService longPollingService, ILogger logger) { _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); + _logger = logger; } [HttpGet("room")] diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index 9c51c01..c02a399 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -23,15 +23,8 @@ public LoggingMiddleware( public async Task Invoke(HttpContext context) { - var requestInformation = - "Request information:\n" + - $"Schema:{context.Request.Scheme}\n" + - $"Content-Type:{context.Request.ContentType}" + - $"Host:{context.Request.Host}\n" + - $"Path:{context.Request.Path}\n" + - $"QueryString:{context.Request.QueryString}\n" + - $"Request Body:{await ObtainRequestBody(context.Request)}\n"; - + var requestInformation = await BuildLog(context); + _logger.LogWarning(requestInformation); var originalResponseBody = context.Response.Body; @@ -43,8 +36,8 @@ public async Task Invoke(HttpContext context) var status = GetStatusCode(context); var level = GetLogLevel(status); - _logger.Log(level, "Response body: LogLevel: {0}; Code: {1}\n Body: {2}", - GetLogLevel(status),status,await ObtainResponseBody(context)); + _logger.Log(level, "Response body: LogLevel: {Enum}; Code: {Status}\n Body: {Body}", + Enum.GetName(GetLogLevel(status)), status.ToString(), await ObtainResponseBody(context)); await responseBody.CopyToAsync(originalResponseBody); @@ -63,6 +56,7 @@ private static async Task ObtainRequestBody(HttpRequest request) request.Body.Seek(0, SeekOrigin.Begin); return bodyStr; } + private static async Task ObtainResponseBody(HttpContext context) { var response = context.Response; @@ -74,13 +68,15 @@ private static async Task ObtainResponseBody(HttpContext context) response.Body.Seek(0, SeekOrigin.Begin); return text; } - private static Encoding GetEncodingFromContentType(string contentTypeStr) + + private static Encoding GetEncodingFromContentType(string? contentTypeStr) { if (string.IsNullOrEmpty(contentTypeStr)) { return Encoding.UTF8; } ContentType contentType; + try { contentType = new ContentType(contentTypeStr); @@ -89,6 +85,7 @@ private static Encoding GetEncodingFromContentType(string contentTypeStr) { return Encoding.UTF8; } + return string.IsNullOrEmpty(contentType.CharSet) ? Encoding.UTF8 : Encoding.GetEncoding(contentType.CharSet, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); @@ -101,5 +98,85 @@ private static LogLevel GetLogLevel(int? statusCode) { return context.Response.StatusCode; } + + private static async Task BuildLog(HttpContext context) + { + var requestBody = await ObtainRequestBody(context.Request); + var length = 89 + + context.Request.Scheme.Length + + context.Request.ContentType?.Length + + context.Request.Host.Host.Length + + (context.Request.Path.HasValue ? context.Request.Path.Value.Length : 0) + + (context.Request.QueryString.HasValue ? context.Request.QueryString.Value!.Length : 0) + + requestBody.Length ?? 0; + + if (length <= 88) + { + return string.Empty; + } + + return string.Create(length, (context, requestBody), (span, tuple) => + { + var index = 0; + var tempString = "Request information:\n"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + tempString = "Schema:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + context.Request.Scheme.CopyTo(span[..index]); + index += context.Request.Scheme.Length; + + "\n".CopyTo(span[..index++]); + + tempString = "Content-Type:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + context.Request.ContentType?.CopyTo(span[..index]); + index += context.Request.ContentType?.Length ?? 0; + + "\n".CopyTo(span[..index++]); + + tempString = "Host:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + context.Request.Host.Host.CopyTo(span[..index]); + index += context.Request.Host.Host.Length; + + "\n".CopyTo(span[..index++]); + + tempString = "Path:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + context.Request.Path.Value?.CopyTo(span[..index]); + index += context.Request.Path.Value?.Length ?? 0; + + "\n".CopyTo(span[..index++]); + + tempString = "Query String:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + context.Request.QueryString.Value?.CopyTo(span[..index]); + index += context.Request.QueryString.Value?.Length ?? 0; + + "\n".CopyTo(span[..index++]); + + tempString = "Request Body:"; + tempString.CopyTo(span[..index]); + index += tempString.Length; + + tuple.requestBody.CopyTo(span[..index]); + index += tuple.requestBody.Length; + + "\n".CopyTo(span[..index]); + + }); + } } \ No newline at end of file diff --git a/Server.Host/Extensions/ServiceExtension.cs b/Server.Host/Extensions/ServiceExtension.cs deleted file mode 100644 index 8f8ec6a..0000000 --- a/Server.Host/Extensions/ServiceExtension.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Server.Bll.Extensions; - -namespace Server.Host.Extensions; - -public static class ServiceExtension -{ - public static IServiceCollection AddServices(this IServiceCollection service) - { - service.AddBusinessLogic(); - - return service; - } -} \ No newline at end of file diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index d3fcbd7..04b3231 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Server.Authentication.Extensions; +using Server.Bll.Extensions; using Server.Data.Context; using Server.Data.Extensions; using Server.Host.Extensions; @@ -27,17 +28,19 @@ public void ConfigureServices(IServiceCollection services) .AddSwagger() .AddAuthentications(); - services.AddServices(); + services.AddBusinessLogic(); services.AddControllers(); services.AddCors(); } - public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, + public static void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, ServerContext serverContext) { - serverContext?.Database.Migrate(); + serverContext.Database.Migrate(); serverContext?.EnsureBotCreated(); if (env.IsDevelopment()) { From 26433e4d902a4339e93dc1ed05672bc26411fac4 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 6 Aug 2022 20:20:26 +0300 Subject: [PATCH 43/56] Fixed Deps --- README.md | 2 +- .../Extensions/AuthenticationExtension.cs | 3 +-- .../Services/AttemptValidationService.cs | 10 +++++----- Server.Authentication/Services/AuthService.cs | 8 ++++---- Server.Bll/Server.Bll.csproj | 2 ++ Server.Bll/Services/RoomService.cs | 3 --- Server.Host/Extensions/LoggingMiddleware.cs | 8 ++++---- Server.Host/Startup.cs | 2 +- global.json | 7 +++++++ 9 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 global.json diff --git a/README.md b/README.md index 924f974..15a199a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # RockPaperScissorsGame -This is second project of Parimatch Tech Academy .NET course. +This is second project of PariMatch Tech Academy .NET course. [Technical requirement](https://docs.google.com/document/d/13lsfyUkMJBZAiXt8OlBWYdKq9zExdHdDhBBH00s5tv4/edit?usp=sharing) diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index c7d8f51..94d13f2 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -46,8 +46,7 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv }; }); - services - .AddTransient(); + services.AddTransient(); return services; } diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index d27a707..03fc773 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -19,7 +19,7 @@ public static bool TryInsertFailAttempt(this string userId) if (failedAttempts >= 2) { // todo: in options - CoolDownCollection.TryAdd(userId, DateTimeOffset.Now.AddMinutes(2)); + CoolDownCollection.TryAdd(userId, DateTimeOffset.UtcNow.AddMinutes(2)); FailedAttempts.TryRemove(userId, out _); return true; @@ -39,16 +39,16 @@ public static bool TryInsertFailAttempt(this string userId) public static bool IsCoolDown(this string userId, out DateTimeOffset coolDownDate) { - var result = CoolDownCollection.TryGetValue(userId, out coolDownDate); - - if (!result) + if (!CoolDownCollection.TryGetValue(userId, out coolDownDate)) { return false; } - if (coolDownDate >= DateTimeOffset.Now) + + if (coolDownDate >= DateTimeOffset.UtcNow) { return true; } + CoolDownCollection.TryRemove(userId, out coolDownDate); return false; diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 855ebe8..9c13436 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -20,13 +20,13 @@ namespace Server.Authentication.Services; /// internal sealed class AuthService : IAuthService { + private static readonly SemaphoreSlim Semaphore = new(initialCount: 1, maxCount: 1); private static readonly JwtSecurityTokenHandler TokenHandler = new(); private static SigningCredentials _signingCredentials = null!; - + private readonly ServerContext _repository; private readonly AuthOptions _authOptions; private readonly ILogger _logger; - private readonly SemaphoreSlim _semaphore = new(1, 1); /// /// Constructor. @@ -60,7 +60,7 @@ public async Task> return new UserException(nameof(password).UserInvalidCredentials()); } - var release = await _semaphore.WaitAsync(100); + var release = await Semaphore.WaitAsync(100); try { @@ -101,7 +101,7 @@ public async Task> { if (release) { - _semaphore.Release(); + Semaphore.Release(); } } } diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index 3a49436..a11618b 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -9,6 +9,8 @@ + + diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index 05e6c4c..a8d4067 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -1,15 +1,12 @@ using System; using System.Linq; using System.Threading.Tasks; -using Mapster; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Hosting; using OneOf; using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using Server.Data.Context; -using Server.Data.Entities; namespace Server.Bll.Services; diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index c02a399..6b2b174 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -37,7 +37,7 @@ public async Task Invoke(HttpContext context) var level = GetLogLevel(status); _logger.Log(level, "Response body: LogLevel: {Enum}; Code: {Status}\n Body: {Body}", - Enum.GetName(GetLogLevel(status)), status.ToString(), await ObtainResponseBody(context)); + Enum.GetName(GetLogLevel(status)), status, await ObtainResponseBody(context)); await responseBody.CopyToAsync(originalResponseBody); @@ -53,6 +53,7 @@ private static async Task ObtainRequestBody(HttpRequest request) { bodyStr = await reader.ReadToEndAsync().ConfigureAwait(false); } + request.Body.Seek(0, SeekOrigin.Begin); return bodyStr; } @@ -62,10 +63,10 @@ private static async Task ObtainResponseBody(HttpContext context) var response = context.Response; response.Body.Seek(0, SeekOrigin.Begin); var encoding = GetEncodingFromContentType(response.ContentType); - using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: - false, bufferSize: 4096, leaveOpen: true); + using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: false, bufferSize: 4096, leaveOpen: true); var text = await reader.ReadToEndAsync().ConfigureAwait(false); response.Body.Seek(0, SeekOrigin.Begin); + return text; } @@ -176,7 +177,6 @@ private static async Task BuildLog(HttpContext context) index += tuple.requestBody.Length; "\n".CopyTo(span[..index]); - }); } } \ No newline at end of file diff --git a/Server.Host/Startup.cs b/Server.Host/Startup.cs index 04b3231..90f9384 100644 --- a/Server.Host/Startup.cs +++ b/Server.Host/Startup.cs @@ -12,7 +12,7 @@ namespace Server.Host; -public class Startup +public sealed class Startup { public Startup(IConfiguration configuration) { diff --git a/global.json b/global.json new file mode 100644 index 0000000..87aef9f --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file From cf0d211622767dd4ad2d6aac71b885bee8f3c604 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 4 Sep 2022 15:46:39 +0300 Subject: [PATCH 44/56] Added trello board --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15a199a..98e44a4 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ This is second project of PariMatch Tech Academy .NET course. [Technical requirement](https://docs.google.com/document/d/13lsfyUkMJBZAiXt8OlBWYdKq9zExdHdDhBBH00s5tv4/edit?usp=sharing) -[Table of progress](https://docs.google.com/spreadsheets/d/1h2YSv9YhTiELJAsEIE5oPusIhlloKiyxRdfDVqGc_JI/edit?usp=sharing) +~~[Table of progress](https://docs.google.com/spreadsheets/d/1h2YSv9YhTiELJAsEIE5oPusIhlloKiyxRdfDVqGc_JI/edit?usp=sharing)~~ + +[Trello Kanban](https://trello.com/b/vofn5Dhn/rock-paper-scissors) From 6794b6951780ac2e539e21c264394fd4d4eb0c7f Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Thu, 13 Oct 2022 23:34:23 +0300 Subject: [PATCH 45/56] Added migration. Removed setters --- Server.Authentication/AuthOptions.cs | 18 +- .../Extensions/AuthenticationExtension.cs | 6 +- .../Models/AccountOutputModel.cs | 4 +- .../Server.Authentication.csproj | 4 +- .../Services/AttemptValidationService.cs | 16 ++ Server.Authentication/Services/AuthService.cs | 40 ++- Server.Bll/Server.Bll.csproj | 2 +- Server.Bll/Services/LongPollingService.cs | 2 +- Server.Data/Context/ServerContext.cs | 12 +- Server.Data/Entities/Account.cs | 4 +- Server.Data/Entities/Player.cs | 2 +- Server.Data/Entities/Room.cs | 2 +- Server.Data/Entities/Round.cs | 5 +- Server.Data/Entities/Statistics.cs | 2 +- ...634_InitialMigrationStringKeys.Designer.cs | 231 ----------------- ...07201712_PlayersCollectionRoom.Designer.cs | 226 ----------------- .../20220507201712_PlayersCollectionRoom.cs | 75 ------ ...01850_PlayersCollectionRoomFix.Designer.cs | 226 ----------------- ...20220507201850_PlayersCollectionRoomFix.cs | 19 -- ...20220507202003_PlayerReadyFlag.Designer.cs | 229 ----------------- .../20220507202003_PlayerReadyFlag.cs | 26 -- .../20220507205137_DateEntities.Designer.cs | 235 ------------------ .../Migrations/20220507205137_DateEntities.cs | 38 --- ...20220508143835_DateEntitiesFix.Designer.cs | 235 ------------------ .../20220508143835_DateEntitiesFix.cs | 19 -- .../20220508144103_DateTimeToLongTicks.cs | 60 ----- ... 20221013195804_InitialCommit.Designer.cs} | 8 +- ...eys.cs => 20221013195804_InitialCommit.cs} | 131 +++++----- .../Migrations/ServerContextModelSnapshot.cs | 4 +- Server.Data/Server.Data.csproj | 4 +- Server.Host/Contracts/AccountDto.cs | 6 +- .../Contracts/Requests/LoginRequest.cs | 8 +- .../Contracts/Requests/RegisterRequest.cs | 4 +- Server.Host/Contracts/StatisticsDto.cs | 4 +- .../Contracts/ViewModels/AccountViewModel.cs | 4 +- .../Controllers/LongPollingController.cs | 4 - Server.Host/Extensions/LoggingMiddleware.cs | 54 ++-- Server.Host/Server.Host.csproj | 8 +- Server.Host/appsettings.Development.json | 9 - 39 files changed, 189 insertions(+), 1797 deletions(-) delete mode 100644 Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs delete mode 100644 Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs delete mode 100644 Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs delete mode 100644 Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs delete mode 100644 Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs delete mode 100644 Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs delete mode 100644 Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs delete mode 100644 Server.Data/Migrations/20220507205137_DateEntities.Designer.cs delete mode 100644 Server.Data/Migrations/20220507205137_DateEntities.cs delete mode 100644 Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs delete mode 100644 Server.Data/Migrations/20220508143835_DateEntitiesFix.cs delete mode 100644 Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs rename Server.Data/Migrations/{20220508144103_DateTimeToLongTicks.Designer.cs => 20221013195804_InitialCommit.Designer.cs} (97%) rename Server.Data/Migrations/{20220507161634_InitialMigrationStringKeys.cs => 20221013195804_InitialCommit.cs} (85%) delete mode 100644 Server.Host/appsettings.Development.json diff --git a/Server.Authentication/AuthOptions.cs b/Server.Authentication/AuthOptions.cs index 8d4849e..4123a39 100644 --- a/Server.Authentication/AuthOptions.cs +++ b/Server.Authentication/AuthOptions.cs @@ -9,37 +9,39 @@ namespace Server.Authentication; /// public sealed class AuthOptions { + private static readonly SymmetricSecurityKey DefaultKey = new(Encoding.ASCII.GetBytes(PrivateKey)); + /// /// Token issuer (producer). /// - public string Issuer { get; set; } = "Rock Paper Scissors"; + public string Issuer { get; init; } = "Rock Paper Scissors"; /// /// Token audience (consumer). /// - public string Audience { get; set; } = "Player"; + public string Audience { get; init; } = "Player"; /// /// Token secret part. /// - public string PrivateKey { get; set; } = "RockPaperScissors"; - + public static string PrivateKey => "RockPaperScissors"; + /// /// Token life time. /// - public TimeSpan LifeTime { get; set; } = TimeSpan.FromHours(3); + public TimeSpan LifeTime { get; init; } = TimeSpan.FromHours(3); /// /// Require HTTPS. /// - public bool RequireHttps { get; set; } = false; + public bool RequireHttps { get; init; } = false; /// /// Getting a symmetric security key. /// /// . - public SymmetricSecurityKey GetSymmetricSecurityKey() + public static SymmetricSecurityKey GetSymmetricSecurityKey() { - return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey)); + return DefaultKey; } } \ No newline at end of file diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server.Authentication/Extensions/AuthenticationExtension.cs index 94d13f2..786c880 100644 --- a/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -18,13 +18,13 @@ public static class AuthenticationExtension /// Service collection. public static IServiceCollection AddAuthentications(this IServiceCollection services) { - _ = services ?? throw new ArgumentNullException(nameof(services)); + ArgumentNullException.ThrowIfNull(services); services.AddOptions(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => - { + { var jwtOptions = services .BuildServiceProvider() .GetRequiredService>() @@ -41,7 +41,7 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv ValidateLifetime = true, ClockSkew = TimeSpan.Zero, - IssuerSigningKey = jwtOptions.GetSymmetricSecurityKey(), + IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(), ValidateIssuerSigningKey = true }; }); diff --git a/Server.Authentication/Models/AccountOutputModel.cs b/Server.Authentication/Models/AccountOutputModel.cs index cf0d56f..d579f0a 100644 --- a/Server.Authentication/Models/AccountOutputModel.cs +++ b/Server.Authentication/Models/AccountOutputModel.cs @@ -8,10 +8,10 @@ public sealed class AccountOutputModel /// /// Gets or sets user token (used in header). /// - public string Token { get; set; } + public string Token { get; init; } /// /// Gets or sets user login. /// - public string Login { get; set; } + public string Login { get; init; } } \ No newline at end of file diff --git a/Server.Authentication/Server.Authentication.csproj b/Server.Authentication/Server.Authentication.csproj index 74970c4..cc13671 100644 --- a/Server.Authentication/Server.Authentication.csproj +++ b/Server.Authentication/Server.Authentication.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server.Authentication/Services/AttemptValidationService.cs index 03fc773..082b598 100644 --- a/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server.Authentication/Services/AttemptValidationService.cs @@ -14,6 +14,11 @@ internal static class AttemptValidationService public static bool TryInsertFailAttempt(this string userId) { + if (string.IsNullOrEmpty(userId)) + { + return false; + } + if (FailedAttempts.TryGetValue(userId, out var failedAttempts)) { if (failedAttempts >= 2) @@ -32,6 +37,11 @@ public static bool TryInsertFailAttempt(this string userId) public static int? CountFailedAttempts(this string userId) { + if (string.IsNullOrEmpty(userId)) + { + return default; + } + return FailedAttempts.TryGetValue(userId, out var failedAttempts) ? failedAttempts : default; @@ -39,6 +49,12 @@ public static bool TryInsertFailAttempt(this string userId) public static bool IsCoolDown(this string userId, out DateTimeOffset coolDownDate) { + if (string.IsNullOrEmpty(userId)) + { + coolDownDate = default; + return false; + } + if (!CoolDownCollection.TryGetValue(userId, out coolDownDate)) { return false; diff --git a/Server.Authentication/Services/AuthService.cs b/Server.Authentication/Services/AuthService.cs index 9c13436..5a7ff91 100644 --- a/Server.Authentication/Services/AuthService.cs +++ b/Server.Authentication/Services/AuthService.cs @@ -20,9 +20,9 @@ namespace Server.Authentication.Services; /// internal sealed class AuthService : IAuthService { + private static readonly SigningCredentials SigningCredentials = new(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256); private static readonly SemaphoreSlim Semaphore = new(initialCount: 1, maxCount: 1); private static readonly JwtSecurityTokenHandler TokenHandler = new(); - private static SigningCredentials _signingCredentials = null!; private readonly ServerContext _repository; private readonly AuthOptions _authOptions; @@ -42,21 +42,24 @@ public AuthService( _logger = logger; _repository = repository; _authOptions = authOptions.Value; - - _signingCredentials = new SigningCredentials(_authOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256); } /// - public async Task> - RegisterAsync(string login, string password) + public async Task> RegisterAsync( + string login, + string password) { if (string.IsNullOrWhiteSpace(login)) { + _logger.LogError("Login should not be 'empty'"); + return new UserException(nameof(login).UserInvalidCredentials()); } if (string.IsNullOrEmpty(password)) { + _logger.LogError("Password should not be 'empty'"); + return new UserException(nameof(password).UserInvalidCredentials()); } @@ -66,7 +69,11 @@ public async Task> { if (await _repository.Accounts.AnyAsync(account => account.Login.Equals(login.ToLower()))) { - return new UserException(login.UserAlreadyExists()); + var exceptionMessage = login.UserAlreadyExists(); + + _logger.LogError("Error occured : {ExceptionMessage}", exceptionMessage); + + return new UserException(exceptionMessage); } var accountId = Guid.NewGuid().ToString(); @@ -111,14 +118,22 @@ public async Task> LoginAsync(string lo { var userAccount = await _repository.Accounts.FirstOrDefaultAsync(account => account.Login.ToLower().Equals(login.ToLower())); + string exceptionMessage; + if (userAccount is null) { - return new UserException(login.UserNotFound()); + exceptionMessage = login.UserNotFound(); + _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); + + return new UserException(exceptionMessage); } if (login.IsCoolDown(out var coolRequestDate)) { - return new UserException(login.UserCoolDown(coolRequestDate)); + exceptionMessage = login.UserCoolDown(coolRequestDate); + _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); + + return new UserException(exceptionMessage); } if (userAccount.Password.IsHashEqual(password)) @@ -131,8 +146,11 @@ public async Task> LoginAsync(string lo } login.TryInsertFailAttempt(); - - return new UserException(login.UserInvalidCredentials()); + + exceptionMessage = login.UserInvalidCredentials(); + _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); + + return new UserException(exceptionMessage); } private string BuildToken(Account accountModel) @@ -146,7 +164,7 @@ private string BuildToken(Account accountModel) now, now.Add(_authOptions.LifeTime), now, - _signingCredentials); + SigningCredentials); return encodedJwt; } diff --git a/Server.Bll/Server.Bll.csproj b/Server.Bll/Server.Bll.csproj index a11618b..5d53c6a 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server.Bll/Server.Bll.csproj @@ -11,7 +11,7 @@ - + diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index 3f922f9..67e9783 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -12,7 +12,7 @@ internal sealed class LongPollingService : ILongPollingService public LongPollingService(ServerContext serverContext) { - _serverContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); + _serverContext = serverContext; } public Task CheckRoomState(string roomId) diff --git a/Server.Data/Context/ServerContext.cs b/Server.Data/Context/ServerContext.cs index 1a09ffa..5e8ea8f 100644 --- a/Server.Data/Context/ServerContext.cs +++ b/Server.Data/Context/ServerContext.cs @@ -5,16 +5,16 @@ namespace Server.Data.Context; public sealed class ServerContext : DbContext { - public DbSet Accounts { get; set; } + public DbSet Accounts { get; init; } - public DbSet Rooms { get; set; } + public DbSet Rooms { get; init; } - public DbSet Players { get; set; } + public DbSet Players { get; init; } - public DbSet Rounds { get; set; } + public DbSet Rounds { get; init; } - public DbSet StatisticsEnumerable { get; set; } + public DbSet StatisticsEnumerable { get; init; } public ServerContext(DbContextOptions contextOptions) :base(contextOptions) { } @@ -22,6 +22,6 @@ public ServerContext(DbContextOptions contextOptions) protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() - .HasQueryFilter(x => !x.IsFinished); + .HasQueryFilter(round => !round.IsFinished); } } \ No newline at end of file diff --git a/Server.Data/Entities/Account.cs b/Server.Data/Entities/Account.cs index bd6d2d8..d3f7d90 100644 --- a/Server.Data/Entities/Account.cs +++ b/Server.Data/Entities/Account.cs @@ -16,12 +16,12 @@ public class Account /// /// Nick name of Account. /// - public string Login { get; set; } + public string Login { get; init; } /// /// Password of the Account /// - public string Password { get; set; } + public string Password { get; init; } /// /// Linked to this player statistics diff --git a/Server.Data/Entities/Player.cs b/Server.Data/Entities/Player.cs index 69cf562..e2e48a3 100644 --- a/Server.Data/Entities/Player.cs +++ b/Server.Data/Entities/Player.cs @@ -11,7 +11,7 @@ public class Player public string Id { get; init; } [ForeignKey("Account")] - public string AccountId { get; set; } + public string AccountId { get; init; } public virtual Account Account { get; set; } diff --git a/Server.Data/Entities/Room.cs b/Server.Data/Entities/Room.cs index 2f82ad4..1df8d95 100644 --- a/Server.Data/Entities/Room.cs +++ b/Server.Data/Entities/Room.cs @@ -17,7 +17,7 @@ public class Room /// /// Special code to join a room /// - public string Code { get; set; } + public string Code { get; init; } /// /// Round, linked to this room diff --git a/Server.Data/Entities/Round.cs b/Server.Data/Entities/Round.cs index 0d1b9eb..18d315c 100644 --- a/Server.Data/Entities/Round.cs +++ b/Server.Data/Entities/Round.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -27,5 +26,5 @@ public class Round public long FinishTimeTicks { get; set; } - public bool IsFinished { get; set; } + public bool IsFinished { get; init; } } \ No newline at end of file diff --git a/Server.Data/Entities/Statistics.cs b/Server.Data/Entities/Statistics.cs index 9343a0d..2d643c6 100644 --- a/Server.Data/Entities/Statistics.cs +++ b/Server.Data/Entities/Statistics.cs @@ -9,7 +9,7 @@ public class Statistics { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string Id { get; set; } + public string Id { get; init; } [ForeignKey("Account")] public string AccountId { get; set; } diff --git a/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs b/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs deleted file mode 100644 index 85755b4..0000000 --- a/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.Designer.cs +++ /dev/null @@ -1,231 +0,0 @@ -// - - -#nullable disable - -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507161634_InitialMigrationStringKeys")] - partial class InitialMigrationStringKeys - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("PlayerId"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.HasOne("Server.Data.Entities.Player", "Player") - .WithMany() - .HasForeignKey("PlayerId"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs deleted file mode 100644 index f7da844..0000000 --- a/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.Designer.cs +++ /dev/null @@ -1,226 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -#nullable disable - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507201712_PlayersCollectionRoom")] - partial class PlayersCollectionRoom - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Room", null) - .WithMany("Player") - .HasForeignKey("RoomId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs b/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs deleted file mode 100644 index 5a0b95d..0000000 --- a/Server.Data/Migrations/20220507201712_PlayersCollectionRoom.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class PlayersCollectionRoom : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Rooms_Players_PlayerId", - table: "Rooms"); - - migrationBuilder.DropIndex( - name: "IX_Rooms_PlayerId", - table: "Rooms"); - - migrationBuilder.DropColumn( - name: "PlayerId", - table: "Rooms"); - - migrationBuilder.AddColumn( - name: "RoomId", - table: "Players", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Players_RoomId", - table: "Players", - column: "RoomId"); - - migrationBuilder.AddForeignKey( - name: "FK_Players_Rooms_RoomId", - table: "Players", - column: "RoomId", - principalTable: "Rooms", - principalColumn: "Id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Players_Rooms_RoomId", - table: "Players"); - - migrationBuilder.DropIndex( - name: "IX_Players_RoomId", - table: "Players"); - - migrationBuilder.DropColumn( - name: "RoomId", - table: "Players"); - - migrationBuilder.AddColumn( - name: "PlayerId", - table: "Rooms", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Rooms_PlayerId", - table: "Rooms", - column: "PlayerId"); - - migrationBuilder.AddForeignKey( - name: "FK_Rooms_Players_PlayerId", - table: "Rooms", - column: "PlayerId", - principalTable: "Players", - principalColumn: "Id"); - } - } -} diff --git a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs deleted file mode 100644 index 840ad43..0000000 --- a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.Designer.cs +++ /dev/null @@ -1,226 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -#nullable disable - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507201850_PlayersCollectionRoomFix")] - partial class PlayersCollectionRoomFix - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Room", null) - .WithMany("Players") - .HasForeignKey("RoomId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Players"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs b/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs deleted file mode 100644 index 3fa172f..0000000 --- a/Server.Data/Migrations/20220507201850_PlayersCollectionRoomFix.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class PlayersCollectionRoomFix : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs deleted file mode 100644 index 581aec2..0000000 --- a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.Designer.cs +++ /dev/null @@ -1,229 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -#nullable disable - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507202003_PlayerReadyFlag")] - partial class PlayerReadyFlag - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Room", null) - .WithMany("Players") - .HasForeignKey("RoomId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Players"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs b/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs deleted file mode 100644 index 71d2c9a..0000000 --- a/Server.Data/Migrations/20220507202003_PlayerReadyFlag.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class PlayerReadyFlag : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IsReady", - table: "Players", - type: "INTEGER", - nullable: false, - defaultValue: false); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IsReady", - table: "Players"); - } - } -} diff --git a/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs b/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs deleted file mode 100644 index 656245d..0000000 --- a/Server.Data/Migrations/20220507205137_DateEntities.Designer.cs +++ /dev/null @@ -1,235 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -#nullable disable - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220507205137_DateEntities")] - partial class DateEntities - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("FinishTime") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("StartTime") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Room", null) - .WithMany("Players") - .HasForeignKey("RoomId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Players"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220507205137_DateEntities.cs b/Server.Data/Migrations/20220507205137_DateEntities.cs deleted file mode 100644 index 1b9211c..0000000 --- a/Server.Data/Migrations/20220507205137_DateEntities.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class DateEntities : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FinishTime", - table: "Rounds", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.AddColumn( - name: "StartTime", - table: "Rounds", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FinishTime", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "StartTime", - table: "Rounds"); - } - } -} diff --git a/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs b/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs deleted file mode 100644 index 93bbbbd..0000000 --- a/Server.Data/Migrations/20220508143835_DateEntitiesFix.Designer.cs +++ /dev/null @@ -1,235 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Server.Data.Context; - -#nullable disable - -namespace Server.Dal.Migrations -{ - [DbContext(typeof(ServerContext))] - [Migration("20220508143835_DateEntitiesFix")] - partial class DateEntitiesFix - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Login") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Accounts"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("IsReady") - .HasColumnType("INTEGER"); - - b.Property("Move") - .HasColumnType("INTEGER"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("RoundId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("AccountId"); - - b.HasIndex("RoomId"); - - b.HasIndex("RoundId"); - - b.ToTable("Players"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Code") - .HasColumnType("TEXT"); - - b.Property("CreationTimeTicks") - .HasColumnType("INTEGER"); - - b.Property("IsFull") - .HasColumnType("INTEGER"); - - b.Property("IsPrivate") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Rooms"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("FinishTime") - .HasColumnType("TEXT"); - - b.Property("IsFinished") - .HasColumnType("INTEGER"); - - b.Property("LoserId") - .HasColumnType("TEXT"); - - b.Property("RoomId") - .HasColumnType("TEXT"); - - b.Property("StartTime") - .HasColumnType("TEXT"); - - b.Property("WinnerId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LoserId"); - - b.HasIndex("RoomId") - .IsUnique(); - - b.HasIndex("WinnerId"); - - b.ToTable("Rounds"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountId") - .HasColumnType("TEXT"); - - b.Property("Draws") - .HasColumnType("INTEGER"); - - b.Property("Loss") - .HasColumnType("INTEGER"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("TimeSpent") - .HasColumnType("TEXT"); - - b.Property("UsedPaper") - .HasColumnType("INTEGER"); - - b.Property("UsedRock") - .HasColumnType("INTEGER"); - - b.Property("UsedScissors") - .HasColumnType("INTEGER"); - - b.Property("WinLossRatio") - .HasColumnType("REAL"); - - b.Property("Wins") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AccountId") - .IsUnique(); - - b.ToTable("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Player", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithMany() - .HasForeignKey("AccountId"); - - b.HasOne("Server.Data.Entities.Room", null) - .WithMany("Players") - .HasForeignKey("RoomId"); - - b.HasOne("Server.Data.Entities.Round", null) - .WithMany("Players") - .HasForeignKey("RoundId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - - b.HasOne("Server.Data.Entities.Room", "Room") - .WithOne("Round") - .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - - b.Navigation("Room"); - - b.Navigation("Winner"); - }); - - modelBuilder.Entity("Server.Data.Entities.Statistics", b => - { - b.HasOne("Server.Data.Entities.Account", "Account") - .WithOne("Statistics") - .HasForeignKey("Server.Data.Entities.Statistics", "AccountId"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Server.Data.Entities.Account", b => - { - b.Navigation("Statistics"); - }); - - modelBuilder.Entity("Server.Data.Entities.Room", b => - { - b.Navigation("Players"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Server.Data.Entities.Round", b => - { - b.Navigation("Players"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs b/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs deleted file mode 100644 index dcdc29f..0000000 --- a/Server.Data/Migrations/20220508143835_DateEntitiesFix.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class DateEntitiesFix : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs b/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs deleted file mode 100644 index 0d55c41..0000000 --- a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Server.Dal.Migrations -{ - public partial class DateTimeToLongTicks : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FinishTime", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "StartTime", - table: "Rounds"); - - migrationBuilder.AddColumn( - name: "FinishTimeTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - - migrationBuilder.AddColumn( - name: "StartTimeTicks", - table: "Rounds", - type: "INTEGER", - nullable: false, - defaultValue: 0L); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FinishTimeTicks", - table: "Rounds"); - - migrationBuilder.DropColumn( - name: "StartTimeTicks", - table: "Rounds"); - - migrationBuilder.AddColumn( - name: "FinishTime", - table: "Rounds", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.AddColumn( - name: "StartTime", - table: "Rounds", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - } -} diff --git a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs b/Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs similarity index 97% rename from Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs rename to Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs index 22c95f3..37b6c41 100644 --- a/Server.Data/Migrations/20220508144103_DateTimeToLongTicks.Designer.cs +++ b/Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs @@ -8,16 +8,16 @@ #nullable disable -namespace Server.Dal.Migrations +namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20220508144103_DateTimeToLongTicks")] - partial class DateTimeToLongTicks + [Migration("20221013195804_InitialCommit")] + partial class InitialCommit { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.10"); modelBuilder.Entity("Server.Data.Entities.Account", b => { diff --git a/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.cs b/Server.Data/Migrations/20221013195804_InitialCommit.cs similarity index 85% rename from Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.cs rename to Server.Data/Migrations/20221013195804_InitialCommit.cs index 52e2d69..4a9f4f8 100644 --- a/Server.Data/Migrations/20220507161634_InitialMigrationStringKeys.cs +++ b/Server.Data/Migrations/20221013195804_InitialCommit.cs @@ -3,9 +3,9 @@ #nullable disable -namespace Server.Dal.Migrations +namespace Server.Data.Migrations { - public partial class InitialMigrationStringKeys : Migration + public partial class InitialCommit : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -22,6 +22,21 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_Accounts", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Rooms", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Code = table.Column(type: "TEXT", nullable: true), + IsPrivate = table.Column(type: "INTEGER", nullable: false), + IsFull = table.Column(type: "INTEGER", nullable: false), + CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rooms", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Statistics", columns: table => new @@ -49,73 +64,66 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "Players", + name: "Rounds", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - AccountId = table.Column(type: "TEXT", nullable: true), - Move = table.Column(type: "INTEGER", nullable: false), - RoundId = table.Column(type: "TEXT", nullable: true) + RoomId = table.Column(type: "TEXT", nullable: true), + WinnerId = table.Column(type: "TEXT", nullable: true), + LoserId = table.Column(type: "TEXT", nullable: true), + StartTimeTicks = table.Column(type: "INTEGER", nullable: false), + FinishTimeTicks = table.Column(type: "INTEGER", nullable: false), + IsFinished = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Players", x => x.Id); + table.PrimaryKey("PK_Rounds", x => x.Id); table.ForeignKey( - name: "FK_Players_Accounts_AccountId", - column: x => x.AccountId, + name: "FK_Rounds_Accounts_LoserId", + column: x => x.LoserId, + principalTable: "Accounts", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Rounds_Accounts_WinnerId", + column: x => x.WinnerId, principalTable: "Accounts", principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "Rooms", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Code = table.Column(type: "TEXT", nullable: true), - PlayerId = table.Column(type: "TEXT", nullable: true), - IsPrivate = table.Column(type: "INTEGER", nullable: false), - IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Rooms", x => x.Id); table.ForeignKey( - name: "FK_Rooms_Players_PlayerId", - column: x => x.PlayerId, - principalTable: "Players", + name: "FK_Rounds_Rooms_RoomId", + column: x => x.RoomId, + principalTable: "Rooms", principalColumn: "Id"); }); migrationBuilder.CreateTable( - name: "Rounds", + name: "Players", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), + AccountId = table.Column(type: "TEXT", nullable: true), + IsReady = table.Column(type: "INTEGER", nullable: false), + Move = table.Column(type: "INTEGER", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), - WinnerId = table.Column(type: "TEXT", nullable: true), - LoserId = table.Column(type: "TEXT", nullable: true), - IsFinished = table.Column(type: "INTEGER", nullable: false) + RoundId = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Rounds", x => x.Id); - table.ForeignKey( - name: "FK_Rounds_Accounts_LoserId", - column: x => x.LoserId, - principalTable: "Accounts", - principalColumn: "Id"); + table.PrimaryKey("PK_Players", x => x.Id); table.ForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - column: x => x.WinnerId, + name: "FK_Players_Accounts_AccountId", + column: x => x.AccountId, principalTable: "Accounts", principalColumn: "Id"); table.ForeignKey( - name: "FK_Rounds_Rooms_RoomId", + name: "FK_Players_Rooms_RoomId", column: x => x.RoomId, principalTable: "Rooms", principalColumn: "Id"); + table.ForeignKey( + name: "FK_Players_Rounds_RoundId", + column: x => x.RoundId, + principalTable: "Rounds", + principalColumn: "Id"); }); migrationBuilder.CreateIndex( @@ -124,14 +132,14 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "AccountId"); migrationBuilder.CreateIndex( - name: "IX_Players_RoundId", + name: "IX_Players_RoomId", table: "Players", - column: "RoundId"); + column: "RoomId"); migrationBuilder.CreateIndex( - name: "IX_Rooms_PlayerId", - table: "Rooms", - column: "PlayerId"); + name: "IX_Players_RoundId", + table: "Players", + column: "RoundId"); migrationBuilder.CreateIndex( name: "IX_Rounds_LoserId", @@ -154,47 +162,24 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Statistics", column: "AccountId", unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Players_Rounds_RoundId", - table: "Players", - column: "RoundId", - principalTable: "Rounds", - principalColumn: "Id"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_Players_Accounts_AccountId", - table: "Players"); - - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_LoserId", - table: "Rounds"); - - migrationBuilder.DropForeignKey( - name: "FK_Rounds_Accounts_WinnerId", - table: "Rounds"); - - migrationBuilder.DropForeignKey( - name: "FK_Players_Rounds_RoundId", - table: "Players"); - migrationBuilder.DropTable( - name: "Statistics"); + name: "Players"); migrationBuilder.DropTable( - name: "Accounts"); + name: "Statistics"); migrationBuilder.DropTable( name: "Rounds"); migrationBuilder.DropTable( - name: "Rooms"); + name: "Accounts"); migrationBuilder.DropTable( - name: "Players"); + name: "Rooms"); } } } diff --git a/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server.Data/Migrations/ServerContextModelSnapshot.cs index 6fc5db1..0905b3a 100644 --- a/Server.Data/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Data/Migrations/ServerContextModelSnapshot.cs @@ -7,7 +7,7 @@ #nullable disable -namespace Server.Dal.Migrations +namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] partial class ServerContextModelSnapshot : ModelSnapshot @@ -15,7 +15,7 @@ partial class ServerContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.10"); modelBuilder.Entity("Server.Data.Entities.Account", b => { diff --git a/Server.Data/Server.Data.csproj b/Server.Data/Server.Data.csproj index 415d72f..6797ec4 100644 --- a/Server.Data/Server.Data.csproj +++ b/Server.Data/Server.Data.csproj @@ -5,11 +5,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Server.Host/Contracts/AccountDto.cs b/Server.Host/Contracts/AccountDto.cs index af4058f..1eb912b 100644 --- a/Server.Host/Contracts/AccountDto.cs +++ b/Server.Host/Contracts/AccountDto.cs @@ -5,9 +5,9 @@ namespace Server.Host.Contracts; public sealed class AccountDto { [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } + public string Login { get; init; } - [Required(ErrorMessage = "Password is required!!")] + [Required(ErrorMessage = "Password is required!")] [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length")] - public string Password { get; set;} + public string Password { get; init;} } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/LoginRequest.cs b/Server.Host/Contracts/Requests/LoginRequest.cs index 83c2274..dc97f28 100644 --- a/Server.Host/Contracts/Requests/LoginRequest.cs +++ b/Server.Host/Contracts/Requests/LoginRequest.cs @@ -6,11 +6,11 @@ namespace Server.Host.Contracts.Requests; public sealed class LoginRequest { [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } + public string Login { get; init; } [Required(ErrorMessage = "Password is required!!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length")] - public string Password { get; set; } + [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length")] + public string Password { get; init; } - public DateTimeOffset LastRequestTime { get; set; } + public DateTimeOffset LastRequestTime { get; init; } } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/RegisterRequest.cs b/Server.Host/Contracts/Requests/RegisterRequest.cs index a0bf63a..e334ace 100644 --- a/Server.Host/Contracts/Requests/RegisterRequest.cs +++ b/Server.Host/Contracts/Requests/RegisterRequest.cs @@ -5,9 +5,9 @@ namespace Server.Host.Contracts.Requests; public sealed class RegisterRequest { [Required(ErrorMessage = "Login is required!")] - public string Login { get; set; } + public string Login { get; init; } [Required(ErrorMessage = "Password is required!")] [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length. Must be 6-20")] - public string Password { get; set; } + public string Password { get; init; } } \ No newline at end of file diff --git a/Server.Host/Contracts/StatisticsDto.cs b/Server.Host/Contracts/StatisticsDto.cs index c5157b2..456df7d 100644 --- a/Server.Host/Contracts/StatisticsDto.cs +++ b/Server.Host/Contracts/StatisticsDto.cs @@ -5,8 +5,8 @@ namespace Server.Host.Contracts; public sealed class StatisticsDto { [JsonPropertyName("Login")] - public string Login { get; set; } + public string Login { get; init; } [JsonPropertyName("Score")] - public int Score { get; set; } + public int Score { get; init; } } \ No newline at end of file diff --git a/Server.Host/Contracts/ViewModels/AccountViewModel.cs b/Server.Host/Contracts/ViewModels/AccountViewModel.cs index 6bc8916..a4d7d6a 100644 --- a/Server.Host/Contracts/ViewModels/AccountViewModel.cs +++ b/Server.Host/Contracts/ViewModels/AccountViewModel.cs @@ -2,7 +2,7 @@ public sealed class AccountViewModel { - public string Token { get; set; } + public string Token { get; init; } - public string Login { get; set; } + public string Login { get; init; } } \ No newline at end of file diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index d6a8b20..59cfa96 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -1,12 +1,10 @@ using System; -using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Server.Authentication.Exceptions; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; @@ -17,12 +15,10 @@ namespace Server.Host.Controllers; public sealed class LongPollingController : ControllerBase { private readonly ILongPollingService _longPollingService; - private readonly ILogger _logger; public LongPollingController(ILongPollingService longPollingService, ILogger logger) { _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); - _logger = logger; } [HttpGet("room")] diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index 6b2b174..a35ba05 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -23,7 +23,7 @@ public LoggingMiddleware( public async Task Invoke(HttpContext context) { - var requestInformation = await BuildLog(context); + var requestInformation = await BuildLogAsync(context); _logger.LogWarning(requestInformation); @@ -37,13 +37,13 @@ public async Task Invoke(HttpContext context) var level = GetLogLevel(status); _logger.Log(level, "Response body: LogLevel: {Enum}; Code: {Status}\n Body: {Body}", - Enum.GetName(GetLogLevel(status)), status, await ObtainResponseBody(context)); + Enum.GetName(GetLogLevel(status)), status, await ObtainResponseBodyAsync(context)); await responseBody.CopyToAsync(originalResponseBody); } - private static async Task ObtainRequestBody(HttpRequest request) + private static async Task ObtainRequestBodyAsync(HttpRequest request) { request.EnableBuffering(); var encoding = GetEncodingFromContentType(request.ContentType); @@ -58,12 +58,15 @@ private static async Task ObtainRequestBody(HttpRequest request) return bodyStr; } - private static async Task ObtainResponseBody(HttpContext context) + private static async Task ObtainResponseBodyAsync(HttpContext context) { var response = context.Response; response.Body.Seek(0, SeekOrigin.Begin); + var encoding = GetEncodingFromContentType(response.ContentType); + using var reader = new StreamReader(response.Body, encoding, detectEncodingFromByteOrderMarks: false, bufferSize: 4096, leaveOpen: true); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); response.Body.Seek(0, SeekOrigin.Begin); @@ -100,9 +103,9 @@ private static LogLevel GetLogLevel(int? statusCode) return context.Response.StatusCode; } - private static async Task BuildLog(HttpContext context) + private static async Task BuildLogAsync(HttpContext context) { - var requestBody = await ObtainRequestBody(context.Request); + var requestBody = await ObtainRequestBodyAsync(context.Request); var length = 89 + context.Request.Scheme.Length + @@ -120,63 +123,64 @@ private static async Task BuildLog(HttpContext context) return string.Create(length, (context, requestBody), (span, tuple) => { var index = 0; + var tempString = "Request information:\n"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; tempString = "Schema:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Scheme.CopyTo(span[..index]); + context.Request.Scheme.CopyTo(span[index..]); index += context.Request.Scheme.Length; - "\n".CopyTo(span[..index++]); + "\n".CopyTo(span[index++..]); tempString = "Content-Type:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.ContentType?.CopyTo(span[..index]); + context.Request.ContentType?.CopyTo(span[index..]); index += context.Request.ContentType?.Length ?? 0; - "\n".CopyTo(span[..index++]); + "\n".CopyTo(span[index++..]); tempString = "Host:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Host.Host.CopyTo(span[..index]); + context.Request.Host.Host.CopyTo(span[index..]); index += context.Request.Host.Host.Length; - "\n".CopyTo(span[..index++]); + "\n".CopyTo(span[index++..]); tempString = "Path:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Path.Value?.CopyTo(span[..index]); + context.Request.Path.Value?.CopyTo(span[index..]); index += context.Request.Path.Value?.Length ?? 0; - "\n".CopyTo(span[..index++]); + "\n".CopyTo(span[index++..]); tempString = "Query String:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.QueryString.Value?.CopyTo(span[..index]); + context.Request.QueryString.Value?.CopyTo(span[index..]); index += context.Request.QueryString.Value?.Length ?? 0; - "\n".CopyTo(span[..index++]); + "\n".CopyTo(span[index++..]); tempString = "Request Body:"; - tempString.CopyTo(span[..index]); + tempString.CopyTo(span[index..]); index += tempString.Length; - tuple.requestBody.CopyTo(span[..index]); + tuple.requestBody.CopyTo(span[index..]); index += tuple.requestBody.Length; - "\n".CopyTo(span[..index]); + "\n".CopyTo(span[index..]); }); } } \ No newline at end of file diff --git a/Server.Host/Server.Host.csproj b/Server.Host/Server.Host.csproj index 32e11e8..5f16570 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server.Host/Server.Host.csproj @@ -6,15 +6,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + diff --git a/Server.Host/appsettings.Development.json b/Server.Host/appsettings.Development.json deleted file mode 100644 index 8983e0f..0000000 --- a/Server.Host/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} From df6f12098d285c45490d73c29964b79bc76ff90b Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sat, 15 Oct 2022 18:03:57 +0300 Subject: [PATCH 46/56] Fixed and updates --- Client/ClientAppEmulator.cs | 8 +- Client/Extensions/JsonExtension.cs | 41 ++- Client/Menus/AccountMenu.cs | 148 ++++----- Client/Menus/IAccountMenu.cs | 15 +- Client/Menus/IMainMenu.cs | 9 +- Client/Menus/MainMenu.cs | 139 ++++---- Client/Menus/StartMenu.cs | 199 ++++++----- Client/Models/Account.cs | 29 +- Client/Models/ErrorModel.cs | 15 +- Client/Models/Interfaces/IAccount.cs | 12 +- .../Models/Interfaces/IOverallStatistics.cs | 13 +- Client/Models/Interfaces/IRoom.cs | 25 +- Client/Models/Interfaces/IRound.cs | 31 +- Client/Models/Interfaces/IStatistics.cs | 105 +++--- Client/Models/Interfaces/StatisticsDto.cs | 21 +- Client/Models/Room.cs | 27 +- Client/Models/Round.cs | 35 +- Client/Models/Statistics.cs | 117 ++++--- Client/Models/StringDestination.cs | 19 +- Client/Models/TokenModel.cs | 12 + Client/Program.cs | 33 +- .../RequestProcessor/IRequestHandler.cs | 16 +- .../RequestProcessor/IRequestPerformer.cs | 16 +- .../RequestProcessor/Impl/RequestHandler.cs | 114 ++++--- .../RequestProcessor/Impl/RequestPerformer.cs | 44 ++- .../RequestModels/IRequestOptions.cs | 24 +- .../RequestModels/IResponse.cs | 13 +- .../RequestModels/Impl/RequestOptions.cs | 26 +- .../RequestModels/Impl/Response.cs | 30 +- .../RequestModels/RequestMethod.cs | 21 +- Client/Services/RoomService.cs | 84 ++--- Client/Services/StatisticsService.cs | 96 +++--- Client/Services/StringPlaceholder.cs | 135 ++++---- Client/Services/TextWrite.cs | 17 +- Client/Validations/StringValidator.cs | 27 +- Server.Bll/Exceptions/ExceptionTemplates.cs | 5 +- Server.Bll/Models/PlayerModel.cs | 4 - Server.Bll/Models/RoomModel.cs | 8 +- .../Interfaces/ILongPollingService.cs | 7 + .../Services/Interfaces/IRoomService.cs | 4 +- .../Services/Interfaces/IStatisticsService.cs | 1 + Server.Bll/Services/LongPollingService.cs | 30 +- Server.Bll/Services/RoomService.cs | 314 ++++++++---------- Server.Bll/Services/RoundService.cs | 29 +- Server.Data/Entities/Account.cs | 2 +- Server.Data/Entities/Player.cs | 4 +- Server.Data/Entities/Room.cs | 7 +- Server.Data/Entities/Round.cs | 10 +- Server.Data/Entities/Statistics.cs | 4 +- Server.Data/Extensions/SeedingExtension.cs | 12 +- ...221015144504_InitialMigration.Designer.cs} | 18 +- ....cs => 20221015144504_InitialMigration.cs} | 84 ++--- .../Migrations/ServerContextModelSnapshot.cs | 14 +- Server.Data/Server.Data.csproj | 4 - Server.Host/Controllers/AccountController.cs | 2 +- .../Controllers/LongPollingController.cs | 19 +- Server.Host/Controllers/RoomController.cs | 15 +- Server.Host/Controllers/RoundController.cs | 2 +- .../Controllers/StatisticsController.cs | 2 +- Server.Host/Extensions/LoggingMiddleware.cs | 26 +- Server.Host/Extensions/SwaggerExtension.cs | 63 ++-- 61 files changed, 1184 insertions(+), 1222 deletions(-) create mode 100644 Client/Models/TokenModel.cs rename Server.Data/Migrations/{20221013195804_InitialCommit.Designer.cs => 20221015144504_InitialMigration.Designer.cs} (94%) rename Server.Data/Migrations/{20221013195804_InitialCommit.cs => 20221015144504_InitialMigration.cs} (74%) diff --git a/Client/ClientAppEmulator.cs b/Client/ClientAppEmulator.cs index 04d0b57..bd40c9c 100644 --- a/Client/ClientAppEmulator.cs +++ b/Client/ClientAppEmulator.cs @@ -1,5 +1,4 @@ -/* -using System; +/*using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -655,7 +654,4 @@ private static void PrintStatistics(IEnumerable statisticsEnumera } } } -} -*/ - - \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Client/Extensions/JsonExtension.cs b/Client/Extensions/JsonExtension.cs index c6a28c7..d7c60ec 100644 --- a/Client/Extensions/JsonExtension.cs +++ b/Client/Extensions/JsonExtension.cs @@ -3,37 +3,36 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; -namespace Client.Extensions +namespace Client.Extensions; + +public static class JsonExtension { - public static class JsonExtension - { - private const string Schema = @"{ + private const string Schema = @"{ 'code': {'type': 'integer'}, 'message': {'type': 'string'}, }"; - private static JSchema JSchema => JSchema.Parse(Schema); + private static JSchema JSchema => JSchema.Parse(Schema); - public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() + public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() + { + if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) { - if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) - { - deserialized = default; - return false; - } + deserialized = default; + return false; + } - var jObject = JObject.Parse(json.Content); + var jObject = JObject.Parse(json.Content); - var isValid = jObject.IsValid(JSchema); + var isValid = jObject.IsValid(JSchema); - if (!isValid) - { - deserialized = default; - return false; - } - - deserialized = JsonConvert.DeserializeObject(json.Content); - return true; + if (!isValid) + { + deserialized = default; + return false; } + + deserialized = JsonConvert.DeserializeObject(json.Content); + return true; } } \ No newline at end of file diff --git a/Client/Menus/AccountMenu.cs b/Client/Menus/AccountMenu.cs index 95fbc80..69ffba3 100644 --- a/Client/Menus/AccountMenu.cs +++ b/Client/Menus/AccountMenu.cs @@ -5,97 +5,97 @@ using Client.Models; using Client.Services; using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels; using Client.Services.RequestProcessor.RequestModels.Impl; using Newtonsoft.Json; -namespace Client.Menus +namespace Client.Menus; + +public class AccountMenu : IAccountMenu { - public class AccountMenu : IAccountMenu + private readonly IRequestPerformer _performer; + + public AccountMenu(IRequestPerformer performer) { - private readonly IRequestPerformer _performer; + _performer = performer; + } - public AccountMenu(IRequestPerformer performer) + public async Task RegisterAsync() + { + TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + + "Please enter the required details\n" + + "to register an account on the platform", ConsoleColor.Magenta); + var registrationAccount = new Account { - _performer = performer; - } + Login = new StringPlaceholder().BuildString("Login"), + Password = + new StringPlaceholder(StringDestination.Password).BuildString("Password") + }; - public async Task RegisterAsync() + var options = new RequestOptions { - TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + - "Please enter the required details\n" + - "to register an account on the platform", ConsoleColor.Magenta); - var registrationAccount = new Account - { - Login = new StringPlaceholder().BuildString("Login"), - Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password") - }; + ContentType = "application/json", + Body = JsonConvert.SerializeObject(registrationAccount), + Address = "account/register", + IsValid = true, + Method = RequestMethod.Post, + Name = "Registration" + }; + var reachedResponse = await _performer.PerformRequestAsync(options); - var options = new RequestOptions - { - ContentType = "application/json", - Body = JsonConvert.SerializeObject(registrationAccount), - Address = "account/register", - IsValid = true, - Method = Services.RequestModels.RequestMethod.Post, - Name = "Registration" - }; - var reachedResponse = await _performer.PerformRequestAsync(options); - - if (reachedResponse.TryParseJson(out var errorModel)) - { - TextWrite.Print(errorModel.Message, ConsoleColor.Red); - return false; - } - - TextWrite.Print("Successfully registered!", ConsoleColor.Green); - return true; + if (reachedResponse.TryParseJson(out var errorModel)) + { + TextWrite.Print(errorModel.Message, ConsoleColor.Red); + return false; } + + TextWrite.Print("Successfully registered!", ConsoleColor.Green); + return true; + } - public async Task<(string token, TokenModel inputAccount)> LoginAsync() + public async Task<(string token, TokenModel inputAccount)> LoginAsync() + { + var inputAccount = new Account + { + Login = new StringPlaceholder().BuildString("Login"), + Password = + new StringPlaceholder(StringDestination.Password).BuildString("Password", true) + }; + var options = new RequestOptions { - var inputAccount = new Account - { - Login = new StringPlaceholder().BuildString("Login"), - Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password", true) - }; - var options = new RequestOptions - { - ContentType = "application/json", - Body = JsonConvert.SerializeObject(inputAccount), - Address = "account/login", - IsValid = true, - Method = Services.RequestModels.RequestMethod.Post, - Name = "Login" - }; - var reachedResponse = await _performer.PerformRequestAsync(options); + ContentType = "application/json", + Body = JsonConvert.SerializeObject(inputAccount), + Address = "account/login", + IsValid = true, + Method = RequestMethod.Post, + Name = "Login" + }; + var reachedResponse = await _performer.PerformRequestAsync(options); - if (reachedResponse.TryParseJson(out var tokenModel)) - { - TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); - return (tokenModel.Token, tokenModel); - } + if (reachedResponse.TryParseJson(out var tokenModel)) + { + TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); + return (tokenModel.Token, tokenModel); + } - var error = JsonConvert.DeserializeObject(reachedResponse.Content); - if(error is null) return (null, null); + var error = JsonConvert.DeserializeObject(reachedResponse.Content); + if(error is null) return (null, null); - TextWrite.Print(error.Message, ConsoleColor.Red); - return (null, null); - } + TextWrite.Print(error.Message, ConsoleColor.Red); + return (null, null); + } - public async Task LogoutAsync(string token) + public async Task LogoutAsync(string token) + { + var options = new RequestOptions { - var options = new RequestOptions - { - Headers = new Dictionary{{"Authorization",token}}, - Address = $"user/logout/{token}", - IsValid = true, - Method = Services.RequestModels.RequestMethod.Get - }; - await _performer.PerformRequestAsync(options); - TextWrite.Print("Successfully signed out", ConsoleColor.Green); - return true; - } + Headers = new Dictionary{{"Authorization",token}}, + Address = $"user/logout/{token}", + IsValid = true, + Method = RequestMethod.Get + }; + await _performer.PerformRequestAsync(options); + TextWrite.Print("Successfully signed out", ConsoleColor.Green); + return true; } } \ No newline at end of file diff --git a/Client/Menus/IAccountMenu.cs b/Client/Menus/IAccountMenu.cs index 50a82c0..71532a0 100644 --- a/Client/Menus/IAccountMenu.cs +++ b/Client/Menus/IAccountMenu.cs @@ -1,12 +1,13 @@ using System.Threading.Tasks; using Client.Models; -namespace Client.Menus +namespace Client.Menus; + +public interface IAccountMenu { - public interface IAccountMenu - { - Task RegisterAsync(); - Task<(string token, TokenModel inputAccount)> LoginAsync(); - Task LogoutAsync(string token); - } + Task RegisterAsync(); + + Task<(string token, TokenModel inputAccount)> LoginAsync(); + + Task LogoutAsync(string token); } \ No newline at end of file diff --git a/Client/Menus/IMainMenu.cs b/Client/Menus/IMainMenu.cs index fd7baaa..39e2eec 100644 --- a/Client/Menus/IMainMenu.cs +++ b/Client/Menus/IMainMenu.cs @@ -1,9 +1,8 @@ using System.Threading.Tasks; -namespace Client.Menus +namespace Client.Menus; + +public interface IMainMenu { - public interface IMainMenu - { - Task PlayerMenu(); - } + Task PlayerMenu(); } \ No newline at end of file diff --git a/Client/Menus/MainMenu.cs b/Client/Menus/MainMenu.cs index 1bd70c7..5c8b80d 100644 --- a/Client/Menus/MainMenu.cs +++ b/Client/Menus/MainMenu.cs @@ -4,84 +4,83 @@ using Client.Services; using Client.Services.RequestProcessor; -namespace Client.Menus +namespace Client.Menus; + +public class MainMenu : IMainMenu { - public class MainMenu : IMainMenu - { - private readonly TokenModel _playerAccount; - private readonly IRoomService _roomService; - private readonly IStatisticsService _statisticsService; + private readonly TokenModel _playerAccount; + private readonly IRoomService _roomService; + private readonly IStatisticsService _statisticsService; - public MainMenu(TokenModel account, - IRequestPerformer requestPerformer, - IStatisticsService statisticsService) - { - _playerAccount = account; - _statisticsService = statisticsService; - _roomService = new RoomService(_playerAccount, requestPerformer); - } + public MainMenu(TokenModel account, + IRequestPerformer requestPerformer, + IStatisticsService statisticsService) + { + _playerAccount = account; + _statisticsService = statisticsService; + _roomService = new RoomService(_playerAccount, requestPerformer); + } - public async Task PlayerMenu() + public async Task PlayerMenu() + { + while (true) { - while (true) - { - TextWrite.Print( - $"***\nHello, {_playerAccount.Login}\n" + - "Please choose option", ConsoleColor.Cyan); - TextWrite.Print( - "1.\tPlay with bot\n" + - "2\tCreate room\n" + - "3\tJoin room\n" + - "4\tSearch open room\n" + - "5\tShow Statistics\n" + - "6\tLog out", ConsoleColor.Yellow); + TextWrite.Print( + $"***\nHello, {_playerAccount.Login}\n" + + "Please choose option", ConsoleColor.Cyan); + TextWrite.Print( + "1.\tPlay with bot\n" + + "2\tCreate room\n" + + "3\tJoin room\n" + + "4\tSearch open room\n" + + "5\tShow Statistics\n" + + "6\tLog out", ConsoleColor.Yellow); - TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); + TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); - Console.Write("Select -> "); - var passed = int.TryParse(Console.ReadLine(), out var playersMenuInput); - if (!passed) - { + Console.Write("Select -> "); + var passed = int.TryParse(Console.ReadLine(), out var playersMenuInput); + if (!passed) + { + TextWrite.Print("Unsupported input", ConsoleColor.Red); + continue; + } + switch (playersMenuInput) + { + case 1: + Console.Clear(); + //await JoinRoomWithBot(); + var room = await _roomService.CreateRoom(true, true); + if (room is null) + return; + //todo: redirect somewhere + break; + case 2: + Console.Clear(); + //await CreationRoom(); + break; + case 3: + Console.Clear(); + //await JoinPrivateRoom(); + break; + case 4: + Console.Clear(); + //await JoinPublicRoom(); + break; + case 5: + Console.Clear(); + var statistics = await _statisticsService + .GetPersonalStatistics(_playerAccount.BearerToken); + Console.WriteLine(statistics+"\n\nPress any key to go back."); + Console.ReadKey(); + break; + case 6: + Console.Clear(); + //await Logout(); + return; + default: TextWrite.Print("Unsupported input", ConsoleColor.Red); continue; - } - switch (playersMenuInput) - { - case 1: - Console.Clear(); - //await JoinRoomWithBot(); - var room = await _roomService.CreateRoom(true, true); - if (room is null) - return; - //todo: redirect somewhere - break; - case 2: - Console.Clear(); - //await CreationRoom(); - break; - case 3: - Console.Clear(); - //await JoinPrivateRoom(); - break; - case 4: - Console.Clear(); - //await JoinPublicRoom(); - break; - case 5: - Console.Clear(); - var statistics = await _statisticsService - .GetPersonalStatistics(_playerAccount.BearerToken); - Console.WriteLine(statistics+"\n\nPress any key to go back."); - Console.ReadKey(); - break; - case 6: - Console.Clear(); - //await Logout(); - return; - default: - TextWrite.Print("Unsupported input", ConsoleColor.Red); - continue; - } } } } diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs index bd186c2..a90ca9f 100644 --- a/Client/Menus/StartMenu.cs +++ b/Client/Menus/StartMenu.cs @@ -5,118 +5,117 @@ using Client.Services; using Client.Services.RequestProcessor; -namespace Client.Menus +namespace Client.Menus; + +public class StartMenu { - public class StartMenu - { - private readonly IAccountMenu _accountMenu; - private readonly IStatisticsService _statisticsService; - private readonly IRequestPerformer _requestPerformer; + private readonly IAccountMenu _accountMenu; + private readonly IStatisticsService _statisticsService; + private readonly IRequestPerformer _requestPerformer; - private string SessionId { get; set; } + private string SessionId { get; set; } - public StartMenu(IRequestPerformer performer) - { - _requestPerformer = performer; - _statisticsService = new StatisticsService(performer); - _accountMenu = new AccountMenu(performer); - } + public StartMenu(IRequestPerformer performer) + { + _requestPerformer = performer; + _statisticsService = new StatisticsService(performer); + _accountMenu = new AccountMenu(performer); + } - public async Task StartAsync() - { - var tokenSource = new CancellationTokenSource(); - var token = tokenSource.Token; + public async Task StartAsync() + { + var tokenSource = new CancellationTokenSource(); + var token = tokenSource.Token; - await Greeting().ConfigureAwait(false); - TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); + await Greeting().ConfigureAwait(false); + TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); - Console.ReadKey(); - Console.Clear(); - //todo: trying to connect to the server - await Menu(token); - return 1; - } - private async Task Menu(CancellationToken token) + Console.ReadKey(); + Console.Clear(); + //todo: trying to connect to the server + await Menu(token); + return 1; + } + private async Task Menu(CancellationToken token) + { + while (true) { - while (true) - { - TextWrite.Print("Start menu:\n" + - "1.\tSign up\n" + - "2.\tLog in\n" + - "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics - "4.\tExit", ConsoleColor.DarkYellow); + TextWrite.Print("Start menu:\n" + + "1.\tSign up\n" + + "2.\tLog in\n" + + "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics + "4.\tExit", ConsoleColor.DarkYellow); - TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); + TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); - Console.Write("Select -> "); - if (token.IsCancellationRequested) - { - return; - } - var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); - if (!passed) - { - TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); - continue; - } - switch (startMenuInput) - { - case 1: - await _accountMenu.RegisterAsync(); - TextWrite.Print( - "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); - Console.ReadKey(); - Console.Clear(); - break; - case 2: - TokenModel inputAccount; - (SessionId, inputAccount) = await _accountMenu.LoginAsync(); - if (!string.IsNullOrEmpty(SessionId)) - { - Console.Clear(); - await new MainMenu(inputAccount,_requestPerformer,_statisticsService) - .PlayerMenu(); - } + Console.Write("Select -> "); + if (token.IsCancellationRequested) + { + return; + } + var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); + if (!passed) + { + TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); + continue; + } + switch (startMenuInput) + { + case 1: + await _accountMenu.RegisterAsync(); + TextWrite.Print( + "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); + Console.ReadKey(); + Console.Clear(); + break; + case 2: + TokenModel inputAccount; + (SessionId, inputAccount) = await _accountMenu.LoginAsync(); + if (!string.IsNullOrEmpty(SessionId)) + { Console.Clear(); - break; - case 3: - var result = await _statisticsService.GetAllStatistics(); - await _statisticsService.PrintStatistics(result); - /*var statistics = await OverallStatistics(); - if(statistics == null) - Console.WriteLine("No statistics so far"); - else - { - PrintStatistics(statistics); - }*/ - break; - case 4: - if (await _accountMenu.LogoutAsync(SessionId)) - { - Console.WriteLine("DEBUG: Logged out"); - return; - } - else - { - throw new NotImplementedException(); - } - default: - TextWrite.Print("Unsupported input", ConsoleColor.Red); - continue; - } + await new MainMenu(inputAccount,_requestPerformer,_statisticsService) + .PlayerMenu(); + } + Console.Clear(); + break; + case 3: + var result = await _statisticsService.GetAllStatistics(); + await _statisticsService.PrintStatistics(result); + /*var statistics = await OverallStatistics(); + if(statistics == null) + Console.WriteLine("No statistics so far"); + else + { + PrintStatistics(statistics); + }*/ + break; + case 4: + if (await _accountMenu.LogoutAsync(SessionId)) + { + Console.WriteLine("DEBUG: Logged out"); + return; + } + else + { + throw new NotImplementedException(); + } + default: + TextWrite.Print("Unsupported input", ConsoleColor.Red); + continue; } } + } - private static Task Greeting() - { - TextWrite.Print( - "VERSION 2.0\n" + - "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + - "You are given the opportunity to compete with other users in this wonderful game,\n" + - "or if you don’t have anyone to play, don’t worry,\n" + - "you can find a random player or just try your skill with a bot.", ConsoleColor.White); - TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); - return Task.CompletedTask; - } + private static Task Greeting() + { + TextWrite.Print( + "VERSION 2.0\n" + + "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + + "You are given the opportunity to compete with other users in this wonderful game,\n" + + "or if you don’t have anyone to play, don’t worry,\n" + + "you can find a random player or just try your skill with a bot.", ConsoleColor.White); + TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs index fc2db6e..4b39ecb 100644 --- a/Client/Models/Account.cs +++ b/Client/Models/Account.cs @@ -1,24 +1,13 @@ -using System; -using Client.Models.Interfaces; +using Client.Models.Interfaces; using Newtonsoft.Json; -namespace Client.Models +namespace Client.Models; + +public class Account : IAccount { - public class Account : IAccount - { - [JsonProperty("Login")] - public string Login { get; set; } + [JsonProperty("Login")] + public string Login { get; set; } - [JsonProperty("Password")] - public string Password { get; set; } - } - - public class TokenModel - { - [JsonIgnore] public string BearerToken => "Bearer " + Token; - [JsonProperty("Token")] - public string Token {get; set; } - [JsonProperty("Login")] - public string Login { get; set; } - } -} + [JsonProperty("Password")] + public string Password { get; set; } +} \ No newline at end of file diff --git a/Client/Models/ErrorModel.cs b/Client/Models/ErrorModel.cs index a9d05c0..9d761ac 100644 --- a/Client/Models/ErrorModel.cs +++ b/Client/Models/ErrorModel.cs @@ -1,12 +1,11 @@ using Newtonsoft.Json; -namespace Client.Models +namespace Client.Models; + +public class ErrorModel { - public class ErrorModel - { - [JsonProperty("Code")] - public int Code { get; set; } - [JsonProperty("Message")] - public string Message { get; set; } - } + [JsonProperty("Code")] + public int Code { get; set; } + [JsonProperty("Message")] + public string Message { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IAccount.cs b/Client/Models/Interfaces/IAccount.cs index 6aaf620..758adc8 100644 --- a/Client/Models/Interfaces/IAccount.cs +++ b/Client/Models/Interfaces/IAccount.cs @@ -1,10 +1,8 @@ -using System; +namespace Client.Models.Interfaces; -namespace Client.Models.Interfaces +public interface IAccount { - public interface IAccount - { - public string Login { get; set; } - public string Password { get; set; } - } + public string Login { get; set; } + + public string Password { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IOverallStatistics.cs b/Client/Models/Interfaces/IOverallStatistics.cs index 3b4d7c8..68484a4 100644 --- a/Client/Models/Interfaces/IOverallStatistics.cs +++ b/Client/Models/Interfaces/IOverallStatistics.cs @@ -1,9 +1,8 @@ -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces; + +public interface IOverallStatistics { - public interface IOverallStatistics - { - Account Account { get; set; } - int Score { get; set; } - - } + Account Account { get; set; } + + int Score { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IRoom.cs b/Client/Models/Interfaces/IRoom.cs index ae76a92..5065dfe 100644 --- a/Client/Models/Interfaces/IRoom.cs +++ b/Client/Models/Interfaces/IRoom.cs @@ -2,21 +2,20 @@ using System.Collections.Concurrent; using Newtonsoft.Json; -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces; + +internal interface IRoom { - internal interface IRoom - { - [JsonProperty("RoomId")] - string RoomId { get; set; } + [JsonProperty("RoomId")] + string RoomId { get; set; } - [JsonProperty("Players")] - ConcurrentDictionary Players { get; set; } + [JsonProperty("Players")] + ConcurrentDictionary Players { get; set; } - [JsonProperty("CurrentRoundId")] - string CurrentRoundId { get; set; } + [JsonProperty("CurrentRoundId")] + string CurrentRoundId { get; set; } - [JsonProperty("CreationTime")] - DateTime CreationTime { get; set; } - bool IsReady { get; set; } - } + [JsonProperty("CreationTime")] + DateTime CreationTime { get; set; } + bool IsReady { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IRound.cs b/Client/Models/Interfaces/IRound.cs index d2f2602..79561a0 100644 --- a/Client/Models/Interfaces/IRound.cs +++ b/Client/Models/Interfaces/IRound.cs @@ -2,26 +2,25 @@ using System.Collections.Concurrent; using Newtonsoft.Json; -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces; + +internal interface IRound { - internal interface IRound - { - [JsonProperty("Id")] - string Id { get; init; } //Not to store identical rounds + [JsonProperty("Id")] + string Id { get; init; } //Not to store identical rounds - [JsonProperty("Moves")] - ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId + [JsonProperty("Moves")] + ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - [JsonProperty("IsFinished")] - bool IsFinished { get; set; } //Probably not needed. + [JsonProperty("IsFinished")] + bool IsFinished { get; set; } //Probably not needed. - [JsonProperty("TimeFinished")] - DateTime TimeFinished { get; set; } + [JsonProperty("TimeFinished")] + DateTime TimeFinished { get; set; } - [JsonProperty("WinnerId")] - string WinnerId { get; set; } + [JsonProperty("WinnerId")] + string WinnerId { get; set; } - [JsonProperty("LoserId")] - string LoserId { get; set; } - } + [JsonProperty("LoserId")] + string LoserId { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IStatistics.cs b/Client/Models/Interfaces/IStatistics.cs index 9317d1d..c9bed6d 100644 --- a/Client/Models/Interfaces/IStatistics.cs +++ b/Client/Models/Interfaces/IStatistics.cs @@ -1,56 +1,55 @@ -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces; + +public interface IStatistics { - public interface IStatistics - { - /// - /// This user account - /// - Account Account { get; set; } - /// - /// Total amount of Wins - /// - int Wins { get; set; } - - /// - /// Total amount of Loses - /// - int Loss { get; set; } - - /// - /// Total amount of Draws. OBSOLETE - /// - - int Draws { get; set; } - - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - double WinLossRatio { get; set; } - - /// - /// Ratio for the last 7 days - /// - string TimeSpent { get; set; } - - /// - /// Times used rock - /// - int UsedRock { get; set; } - - /// - /// Times used Paper - /// - int UsedPaper { get; set; } - - /// - /// Times used Scissors - /// - int UsedScissors { get; set; } - - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - int Score { get; set; } + /// + /// This user account + /// + Account Account { get; set; } + /// + /// Total amount of Wins + /// + int Wins { get; set; } + + /// + /// Total amount of Loses + /// + int Loss { get; set; } + + /// + /// Total amount of Draws. OBSOLETE + /// + + int Draws { get; set; } + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + double WinLossRatio { get; set; } + + /// + /// Ratio for the last 7 days + /// + string TimeSpent { get; set; } + + /// + /// Times used rock + /// + int UsedRock { get; set; } + + /// + /// Times used Paper + /// + int UsedPaper { get; set; } + + /// + /// Times used Scissors + /// + int UsedScissors { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + int Score { get; set; } - } } \ No newline at end of file diff --git a/Client/Models/Interfaces/StatisticsDto.cs b/Client/Models/Interfaces/StatisticsDto.cs index 8b6d390..2aa1e20 100644 --- a/Client/Models/Interfaces/StatisticsDto.cs +++ b/Client/Models/Interfaces/StatisticsDto.cs @@ -1,18 +1,17 @@ using Newtonsoft.Json; -namespace Client.Models.Interfaces +namespace Client.Models.Interfaces; + +public class StatisticsDto { - public class StatisticsDto - { - [JsonProperty("Login")] - public string Login { get; set; } + [JsonProperty("Login")] + public string Login { get; set; } - [JsonProperty("Score")] - public int Score { get; set; } + [JsonProperty("Score")] + public int Score { get; set; } - public override string ToString() - { - return $"Login: {Login} ; Score: {Score}\n"; - } + public override string ToString() + { + return $"Login: {Login} ; Score: {Score}\n"; } } \ No newline at end of file diff --git a/Client/Models/Room.cs b/Client/Models/Room.cs index 18d1cd5..53b8eb0 100644 --- a/Client/Models/Room.cs +++ b/Client/Models/Room.cs @@ -3,22 +3,21 @@ using System.Collections.Concurrent; using Client.Models.Interfaces; -namespace Client.Models +namespace Client.Models; + +public class Room : IRoom { - public class Room : IRoom - { - [JsonProperty("RoomId")] - public string RoomId { get; set; } + [JsonProperty("RoomId")] + public string RoomId { get; set; } - [JsonProperty("Players")] - public ConcurrentDictionary Players { get; set; } + [JsonProperty("Players")] + public ConcurrentDictionary Players { get; set; } - [JsonProperty("CurrentRoundId")] - public string CurrentRoundId { get; set; } + [JsonProperty("CurrentRoundId")] + public string CurrentRoundId { get; set; } - [JsonProperty("CreationTime")] - public DateTime CreationTime { get; set; } + [JsonProperty("CreationTime")] + public DateTime CreationTime { get; set; } - public bool IsReady { get; set; } - } -} + public bool IsReady { get; set; } +} \ No newline at end of file diff --git a/Client/Models/Round.cs b/Client/Models/Round.cs index 7bfefc7..1449c9b 100644 --- a/Client/Models/Round.cs +++ b/Client/Models/Round.cs @@ -3,29 +3,28 @@ using System.Collections.Concurrent; using Client.Models.Interfaces; -namespace Client.Models +namespace Client.Models; + +internal class Round : IRound { - internal class Round : IRound - { - [JsonProperty("Id")] - public string Id { get; init; } //Not to store identical rounds + [JsonProperty("Id")] + public string Id { get; init; } //Not to store identical rounds - [JsonProperty("Moves")] - public ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId + [JsonProperty("Moves")] + public ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - [JsonProperty("IsFinished")] - public bool IsFinished { get; set; } //Probably not needed. + [JsonProperty("IsFinished")] + public bool IsFinished { get; set; } //Probably not needed. - [JsonProperty("TimeFinished")] - public DateTime TimeFinished { get; set; } + [JsonProperty("TimeFinished")] + public DateTime TimeFinished { get; set; } - [JsonProperty("WinnerId")] - public string WinnerId { get; set; } + [JsonProperty("WinnerId")] + public string WinnerId { get; set; } - [JsonProperty("LoserId")] - public string LoserId { get; set; } + [JsonProperty("LoserId")] + public string LoserId { get; set; } - [JsonProperty("IsDraw")] - public bool IsDraw { get; set; } - } + [JsonProperty("IsDraw")] + public bool IsDraw { get; set; } } \ No newline at end of file diff --git a/Client/Models/Statistics.cs b/Client/Models/Statistics.cs index 6ca0d8e..ce99282 100644 --- a/Client/Models/Statistics.cs +++ b/Client/Models/Statistics.cs @@ -1,76 +1,75 @@ using Client.Models.Interfaces; -namespace Client.Models +namespace Client.Models; + +public class Statistics : IStatistics, IOverallStatistics { - public class Statistics : IStatistics, IOverallStatistics - { - /// - /// user - /// - public Account Account { get; set; } - /// - /// Total amount of Wins - /// - public int Wins { get; set; } + /// + /// user + /// + public Account Account { get; set; } + /// + /// Total amount of Wins + /// + public int Wins { get; set; } - /// - /// Total amount of Loses - /// - public int Loss { get; set; } + /// + /// Total amount of Loses + /// + public int Loss { get; set; } - /// - /// Total amount of Draws. OBSOLETE - /// + /// + /// Total amount of Draws. OBSOLETE + /// - public int Draws { get; set; } + public int Draws { get; set; } - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - public double WinLossRatio { get; set; } + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + public double WinLossRatio { get; set; } - /// - /// Ratio for the last 7 days - /// - public string TimeSpent { get; set; } + /// + /// Ratio for the last 7 days + /// + public string TimeSpent { get; set; } - /// - /// Times used rock - /// - public int UsedRock { get; set; } + /// + /// Times used rock + /// + public int UsedRock { get; set; } - /// - /// Times used Paper - /// - public int UsedPaper { get; set; } + /// + /// Times used Paper + /// + public int UsedPaper { get; set; } - /// - /// Times used Scissors - /// - public int UsedScissors { get; set; } + /// + /// Times used Scissors + /// + public int UsedScissors { get; set; } - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - public int Score { get; set; } + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + public int Score { get; set; } - public override string ToString() - { - return $"Times Won: {Wins}\n" + - $"Times Lost:{Loss}\n" + - $"Win to Loss ratio: {WinLossRatio}\n" + - $"{TimeSpent}\n" + - $"Times used rock: {UsedRock}\n" + - $"Times used paper: {UsedPaper}\n" + - $"Times used scissors: {UsedScissors}\n" + - $"Total score: {Score}"; - } + public override string ToString() + { + return $"Times Won: {Wins}\n" + + $"Times Lost:{Loss}\n" + + $"Win to Loss ratio: {WinLossRatio}\n" + + $"{TimeSpent}\n" + + $"Times used rock: {UsedRock}\n" + + $"Times used paper: {UsedPaper}\n" + + $"Times used scissors: {UsedScissors}\n" + + $"Total score: {Score}"; + } - public string ToShortString() - { - return $"UserName: {Account.Login}" + - $"Score: {Score}"; - } + public string ToShortString() + { + return $"UserName: {Account.Login}" + + $"Score: {Score}"; } } \ No newline at end of file diff --git a/Client/Models/StringDestination.cs b/Client/Models/StringDestination.cs index 4e02c13..2a0c63d 100644 --- a/Client/Models/StringDestination.cs +++ b/Client/Models/StringDestination.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace Client.Models; -namespace Client.Models +public enum StringDestination { - public enum StringDestination - { - Login, - Password, - Email, - PassportType //If firstname, lastname...(data without digits!) - } -} + Login, + Password, + Email, + PassportType //If firstname, lastname...(data without digits!) +} \ No newline at end of file diff --git a/Client/Models/TokenModel.cs b/Client/Models/TokenModel.cs new file mode 100644 index 0000000..850e0cd --- /dev/null +++ b/Client/Models/TokenModel.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Client.Models; + +public class TokenModel +{ + [JsonIgnore] public string BearerToken => "Bearer " + Token; + [JsonProperty("Token")] + public string Token {get; set; } + [JsonProperty("Login")] + public string Login { get; set; } +} \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs index 42aa15d..e11d105 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -4,27 +4,26 @@ using System.Threading.Tasks; using Client.Menus; -namespace Client +namespace Client; + +internal static class Program { - internal static class Program + private static async Task Main() { - private static async Task Main() + try { - try - { - var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; - var clientHandler = new HttpClientHandler(); - var requestHandler = new RequestHandler(client, clientHandler); - var requestPerformer = new RequestPerformer(requestHandler); + var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; + var clientHandler = new HttpClientHandler(); + var requestHandler = new RequestHandler(client, clientHandler); + var requestPerformer = new RequestPerformer(requestHandler); - var startMenu = new StartMenu(requestPerformer); - return await startMenu.StartAsync(); - } - catch (Exception) //todo : do this need a message? - { - Console.WriteLine("Unknown error occured. Crash."); - return -1; - } + var startMenu = new StartMenu(requestPerformer); + return await startMenu.StartAsync(); + } + catch (Exception) //todo : do this need a message? + { + Console.WriteLine("Unknown error occured. Crash."); + return -1; } } } \ No newline at end of file diff --git a/Client/Services/RequestProcessor/IRequestHandler.cs b/Client/Services/RequestProcessor/IRequestHandler.cs index fed8de0..17295b8 100644 --- a/Client/Services/RequestProcessor/IRequestHandler.cs +++ b/Client/Services/RequestProcessor/IRequestHandler.cs @@ -1,13 +1,9 @@ -using Client.Services.RequestModels; -using System; -using System.Net.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; using Client.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor +namespace Client.Services.RequestProcessor; + +public interface IRequestHandler { - public interface IRequestHandler - { - Task HandleRequestAsync(IRequestOptions requestOptions); - } -} + Task HandleRequestAsync(IRequestOptions requestOptions); +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/IRequestPerformer.cs b/Client/Services/RequestProcessor/IRequestPerformer.cs index 68aa3c8..4272be9 100644 --- a/Client/Services/RequestProcessor/IRequestPerformer.cs +++ b/Client/Services/RequestProcessor/IRequestPerformer.cs @@ -1,13 +1,9 @@ -using Client.Services.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Client.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor +namespace Client.Services.RequestProcessor; + +public interface IRequestPerformer { - public interface IRequestPerformer - { - Task PerformRequestAsync(IRequestOptions requestOptions); - } -} + Task PerformRequestAsync(IRequestOptions requestOptions); +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Services/RequestProcessor/Impl/RequestHandler.cs index 8d765e9..64d4fd8 100644 --- a/Client/Services/RequestProcessor/Impl/RequestHandler.cs +++ b/Client/Services/RequestProcessor/Impl/RequestHandler.cs @@ -1,78 +1,76 @@ -using Client.Services.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; +using Client.Services.RequestProcessor.RequestModels.Impl; using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Client.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor.Impl +namespace Client.Services.RequestProcessor.Impl; + +public class RequestHandler : IRequestHandler { - public class RequestHandler : IRequestHandler + private readonly HttpClient _client; + private readonly HttpClientHandler _httpClientHandler; + + public RequestHandler(HttpClient httpClient, HttpClientHandler httpClientHandler) { - private readonly HttpClient _client; - private readonly HttpClientHandler _httpClientHandler; + _client = httpClient; + _httpClientHandler = httpClientHandler; + } + public async Task HandleRequestAsync(IRequestOptions requestOptions) + { + //var handler = new HttpClientHandler(); + _httpClientHandler + .ServerCertificateCustomValidationCallback + += (_, _, _, _) => true; + if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); + if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - public RequestHandler(HttpClient httpClient, HttpClientHandler httpClientHandler) - { - _client = httpClient; - _httpClientHandler = httpClientHandler; - } - public async Task HandleRequestAsync(IRequestOptions requestOptions) + using var msg = + new HttpRequestMessage(MapMethod(requestOptions.Method), + new Uri(_client.BaseAddress+requestOptions.Address)); + try { - //var handler = new HttpClientHandler(); - _httpClientHandler - .ServerCertificateCustomValidationCallback - += (_, _, _, _) => true; - if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); - if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - - using var msg = - new HttpRequestMessage(MapMethod(requestOptions.Method), - new Uri(_client.BaseAddress+requestOptions.Address)); - try - { - if(requestOptions.Headers != null) - foreach (var (key, value) in requestOptions.Headers) - { - msg.Headers.Add(key,value); - } - - if (MapMethod(requestOptions.Method) == HttpMethod.Delete) + if(requestOptions.Headers != null) + foreach (var (key, value) in requestOptions.Headers) { - using var responseD = await _client.SendAsync(msg); - var bodyD = await responseD.Content.ReadAsStringAsync(); - return new Response(true, (int)responseD.StatusCode, bodyD); - } - - if (MapMethod(requestOptions.Method) != HttpMethod.Get) - { - msg.Content = new StringContent(requestOptions.Body, Encoding.UTF8, requestOptions.ContentType); - using var responseForPushingData = await _client.SendAsync(msg); - var bodyForPushing = await responseForPushingData.Content.ReadAsStringAsync(); - return new Response(true, (int)responseForPushingData.StatusCode, bodyForPushing); + msg.Headers.Add(key,value); } - using var response = await _client.SendAsync(msg); - var body = await response.Content.ReadAsStringAsync(); - return new Response(true, (int)response.StatusCode, body); + if (MapMethod(requestOptions.Method) == HttpMethod.Delete) + { + using var responseD = await _client.SendAsync(msg); + var bodyD = await responseD.Content.ReadAsStringAsync(); + return new Response(true, (int)responseD.StatusCode, bodyD); } - catch (HttpRequestException) //todo: probably redo + + if (MapMethod(requestOptions.Method) != HttpMethod.Get) { - return new Response(false, 500, "Server.Host is not responding!"); + msg.Content = new StringContent(requestOptions.Body, Encoding.UTF8, requestOptions.ContentType); + using var responseForPushingData = await _client.SendAsync(msg); + var bodyForPushing = await responseForPushingData.Content.ReadAsStringAsync(); + return new Response(true, (int)responseForPushingData.StatusCode, bodyForPushing); } + + using var response = await _client.SendAsync(msg); + var body = await response.Content.ReadAsStringAsync(); + return new Response(true, (int)response.StatusCode, body); } - private static HttpMethod MapMethod(RequestMethod method) + catch (HttpRequestException) //todo: probably redo { - return method switch - { - RequestMethod.Get => HttpMethod.Get, - RequestMethod.Post => HttpMethod.Post, - RequestMethod.Put => HttpMethod.Put, - RequestMethod.Patch => HttpMethod.Patch, - RequestMethod.Delete => HttpMethod.Delete, - _ => throw new ArgumentOutOfRangeException(nameof(method), method, "Invalid request method") - }; + return new Response(false, 500, "Server.Host is not responding!"); } } -} + private static HttpMethod MapMethod(RequestMethod method) + { + return method switch + { + RequestMethod.Get => HttpMethod.Get, + RequestMethod.Post => HttpMethod.Post, + RequestMethod.Put => HttpMethod.Put, + RequestMethod.Patch => HttpMethod.Patch, + RequestMethod.Delete => HttpMethod.Delete, + _ => throw new ArgumentOutOfRangeException(nameof(method), method, "Invalid request method") + }; + } +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs index 62a572b..4f1b401 100644 --- a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs +++ b/Client/Services/RequestProcessor/Impl/RequestPerformer.cs @@ -1,34 +1,28 @@ -using Client.Services.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Threading.Tasks; using Client.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor.Impl +namespace Client.Services.RequestProcessor.Impl; + +public class RequestPerformer : IRequestPerformer { - public class RequestPerformer : IRequestPerformer + private readonly IRequestHandler _requestHandler; + public RequestPerformer(IRequestHandler requestHandler) { - private readonly IRequestHandler _requestHandler; - public RequestPerformer(IRequestHandler requestHandler) + _requestHandler = requestHandler; + } + public async Task PerformRequestAsync(IRequestOptions requestOptions) + { + if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); + if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); + try { - _requestHandler = requestHandler; + return await _requestHandler.HandleRequestAsync(requestOptions); } - public async Task PerformRequestAsync(IRequestOptions requestOptions) - { - if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); - if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - try - { - return await _requestHandler.HandleRequestAsync(requestOptions); - } - catch (TimeoutException) //todo: Probably redo - { - //response = new Response(false, 408, null); - return null; - } + catch (TimeoutException) //todo: Probably redo + { + //response = new Response(false, 408, null); + return null; } } -} +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs b/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs index 75c1475..de6583e 100644 --- a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs +++ b/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs @@ -1,16 +1,14 @@ using System.Collections.Generic; -using Client.Services.RequestModels; -namespace Client.Services.RequestProcessor.RequestModels +namespace Client.Services.RequestProcessor.RequestModels; + +public interface IRequestOptions { - public interface IRequestOptions - { - Dictionary Headers { get; } - string Name { get; } - string Address { get; } - RequestMethod Method { get; } - string ContentType { get; } - string Body { get; } - bool IsValid { get; } - } -} + Dictionary Headers { get; } + string Name { get; } + string Address { get; } + RequestMethod Method { get; } + string ContentType { get; } + string Body { get; } + bool IsValid { get; } +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/IResponse.cs b/Client/Services/RequestProcessor/RequestModels/IResponse.cs index 2d84061..13f7506 100644 --- a/Client/Services/RequestProcessor/RequestModels/IResponse.cs +++ b/Client/Services/RequestProcessor/RequestModels/IResponse.cs @@ -1,9 +1,8 @@ -namespace Client.Services.RequestProcessor.RequestModels +namespace Client.Services.RequestProcessor.RequestModels; + +public interface IResponse { - public interface IResponse - { - public bool Handled { get; } - public int Code { get; } - public string Content { get; } - } + public bool Handled { get; } + public int Code { get; } + public string Content { get; } } \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs b/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs index fd90722..8d6f076 100644 --- a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs +++ b/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs @@ -1,25 +1,23 @@ -using Client.Services.RequestModels; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Client.Services.RequestProcessor.RequestModels.Impl +namespace Client.Services.RequestProcessor.RequestModels.Impl; + +public class RequestOptions : IRequestOptions { - public class RequestOptions : IRequestOptions - { - public Dictionary Headers { get; set; } - public string Name { get; set; } + public Dictionary Headers { get; set; } + public string Name { get; set; } - public string Address { get; set; } + public string Address { get; set; } - public RequestMethod Method { get; set; } + public RequestMethod Method { get; set; } - public string ContentType { get; set; } + public string ContentType { get; set; } - public string Body { get; set; } + public string Body { get; set; } - public bool IsValid { get; set; } - } -} + public bool IsValid { get; set; } +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs b/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs index d5703e8..fa67569 100644 --- a/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs +++ b/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs @@ -1,24 +1,16 @@ -using Client.Services.RequestModels; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace Client.Services.RequestProcessor.RequestModels.Impl; -namespace Client.Services.RequestProcessor.RequestModels.Impl +public class Response : IResponse { - public class Response : IResponse + public Response(bool handled, int code, string content) { - public Response(bool handled, int code, string content) - { - Handled = handled; - Code = code; - Content = content; - } - public bool Handled { get; set; } + Handled = handled; + Code = code; + Content = content; + } + public bool Handled { get; set; } - public int Code { get; set; } + public int Code { get; set; } - public string Content { get; set; } - } -} + public string Content { get; set; } +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs b/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs index 893fe81..88a7555 100644 --- a/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs +++ b/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs @@ -1,12 +1,11 @@ -namespace Client.Services.RequestModels +namespace Client.Services.RequestProcessor.RequestModels; + +public enum RequestMethod { - public enum RequestMethod - { - Undefined = 0, - Get = 1, - Post = 2, - Put = 3, - Patch = 4, - Delete =5 - } -} + Undefined = 0, + Get = 1, + Post = 2, + Put = 3, + Patch = 4, + Delete =5 +} \ No newline at end of file diff --git a/Client/Services/RoomService.cs b/Client/Services/RoomService.cs index de45a58..cfe9336 100644 --- a/Client/Services/RoomService.cs +++ b/Client/Services/RoomService.cs @@ -4,59 +4,59 @@ using Client.Extensions; using Client.Models; using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels; using Client.Services.RequestProcessor.RequestModels.Impl; using Newtonsoft.Json; -namespace Client.Services +namespace Client.Services; + +public class RoomService : IRoomService { - public class RoomService : IRoomService - { - private readonly TokenModel _tokenModel; - private readonly IRequestPerformer _requestPerformer; + private readonly TokenModel _tokenModel; + private readonly IRequestPerformer _requestPerformer; - public RoomService(TokenModel tokenModel, - IRequestPerformer requestPerformer) - { - _tokenModel = tokenModel; - _requestPerformer = requestPerformer; - } - public async Task CreateRoom(bool isPrivate, bool isTraining) - { - Console.WriteLine("Trying to create a room."); + public RoomService(TokenModel tokenModel, + IRequestPerformer requestPerformer) + { + _tokenModel = tokenModel; + _requestPerformer = requestPerformer; + } + public async Task CreateRoom(bool isPrivate, bool isTraining) + { + Console.WriteLine("Trying to create a room."); - var options = new RequestOptions + var options = new RequestOptions + { + Address = $"room/create?isPrivate={isPrivate}", + IsValid = true, + Headers = new Dictionary { - Address = $"room/create?isPrivate={isPrivate}", - IsValid = true, - Headers = new Dictionary { - { - "Authorization", _tokenModel.BearerToken - }, - { - "X-Training", isTraining.ToString() - } + "Authorization", _tokenModel.BearerToken }, - Method = RequestModels.RequestMethod.Post, - Body = string.Empty, - Name = "Create Room" - }; - var reachedResponse = await _requestPerformer - .PerformRequestAsync(options); + { + "X-Training", isTraining.ToString() + } + }, + Method = RequestMethod.Post, + Body = string.Empty, + Name = "Create Room" + }; + var reachedResponse = await _requestPerformer + .PerformRequestAsync(options); - if (reachedResponse.TryParseJson(out var errorModel)) - { - TextWrite.Print(errorModel.Message, ConsoleColor.Red); - return null; - } - - var room = JsonConvert.DeserializeObject(reachedResponse.Content); - return room; + if (reachedResponse.TryParseJson(out var errorModel)) + { + TextWrite.Print(errorModel.Message, ConsoleColor.Red); + return null; } - } - public interface IRoomService - { - Task CreateRoom(bool isPrivate, bool isTraining); + var room = JsonConvert.DeserializeObject(reachedResponse.Content); + return room; } +} + +public interface IRoomService +{ + Task CreateRoom(bool isPrivate, bool isTraining); } \ No newline at end of file diff --git a/Client/Services/StatisticsService.cs b/Client/Services/StatisticsService.cs index 652af2b..c9e73c6 100644 --- a/Client/Services/StatisticsService.cs +++ b/Client/Services/StatisticsService.cs @@ -4,71 +4,71 @@ using Client.Models; using Client.Models.Interfaces; using Client.Services.RequestProcessor; +using Client.Services.RequestProcessor.RequestModels; using Client.Services.RequestProcessor.RequestModels.Impl; using Mapster; using Newtonsoft.Json; -namespace Client.Services +namespace Client.Services; + +public class StatisticsService: IStatisticsService { - public class StatisticsService: IStatisticsService - { - private readonly IRequestPerformer _requestPerformer; + private readonly IRequestPerformer _requestPerformer; - public StatisticsService(IRequestPerformer requestPerformer) - { - _requestPerformer = requestPerformer; - } + public StatisticsService(IRequestPerformer requestPerformer) + { + _requestPerformer = requestPerformer; + } - public async Task GetAllStatistics() + public async Task GetAllStatistics() + { + var options = new RequestOptions { - var options = new RequestOptions - { - ContentType = "none", - Address = "stats/all", - IsValid = true, - Method = RequestModels.RequestMethod.Get, - Name = "OverallStats" - }; + ContentType = "none", + Address = "stats/all", + IsValid = true, + Method = RequestMethod.Get, + Name = "OverallStats" + }; - var response = await _requestPerformer.PerformRequestAsync(options); + var response = await _requestPerformer.PerformRequestAsync(options); - var toConvert = JsonConvert.DeserializeObject(response.Content); - return toConvert?.Adapt(); - } + var toConvert = JsonConvert.DeserializeObject(response.Content); + return toConvert?.Adapt(); + } - public async Task GetPersonalStatistics(string token) + public async Task GetPersonalStatistics(string token) + { + var options = new RequestOptions { - var options = new RequestOptions - { - Headers = new Dictionary{{"Authorization",token}}, - ContentType = "none", - Address = "stats/personal", - IsValid = true, - Method = RequestModels.RequestMethod.Get, - Name = "PersonalStats" - }; + Headers = new Dictionary{{"Authorization",token}}, + ContentType = "none", + Address = "stats/personal", + IsValid = true, + Method = RequestMethod.Get, + Name = "PersonalStats" + }; - var response = await _requestPerformer.PerformRequestAsync(options); + var response = await _requestPerformer.PerformRequestAsync(options); - return response.Content != null ? JsonConvert.DeserializeObject(response.Content) : null; - } + return response.Content != null ? JsonConvert.DeserializeObject(response.Content) : null; + } - public Task PrintStatistics(IOverallStatistics[] statistics) + public Task PrintStatistics(IOverallStatistics[] statistics) + { + for(var i = 0; i < statistics.Length; i++) { - for(var i = 0; i < statistics.Length; i++) - { - TextWrite.Print($"{i+1}. User: {statistics[i].Account.Login}\n" + - $"Score: {statistics[i].Score}", ConsoleColor.White); - } - Console.Write('\n'); - return Task.CompletedTask; + TextWrite.Print($"{i+1}. User: {statistics[i].Account.Login}\n" + + $"Score: {statistics[i].Score}", ConsoleColor.White); } + Console.Write('\n'); + return Task.CompletedTask; } +} - public interface IStatisticsService - { - Task GetAllStatistics(); - Task GetPersonalStatistics(string token); - Task PrintStatistics(IOverallStatistics[] statistics); - } +public interface IStatisticsService +{ + Task GetAllStatistics(); + Task GetPersonalStatistics(string token); + Task PrintStatistics(IOverallStatistics[] statistics); } \ No newline at end of file diff --git a/Client/Services/StringPlaceholder.cs b/Client/Services/StringPlaceholder.cs index abffa3a..db2f8f6 100644 --- a/Client/Services/StringPlaceholder.cs +++ b/Client/Services/StringPlaceholder.cs @@ -2,82 +2,81 @@ using Client.Models; using Client.Validations; -namespace Client.Services +namespace Client.Services; + +internal class StringPlaceholder { - internal class StringPlaceholder + private readonly StringDestination _destination; + public StringPlaceholder() { - private readonly StringDestination _destination; - public StringPlaceholder() - { - _destination = StringDestination.Login; - } - public StringPlaceholder(StringDestination destination) - { - _destination = destination; - } - public string BuildString(string msg, bool isNeedConfirmation = false) + _destination = StringDestination.Login; + } + public StringPlaceholder(StringDestination destination) + { + _destination = destination; + } + public string BuildString(string msg, bool isNeedConfirmation = false) + { + string output; + while (true) { - string output; - while (true) - { - var passwordNotConfirmed = true; - TextWrite.Print( - _destination is StringDestination.PassportType or StringDestination.Email - ? $"What is your {msg}?" - : $"Try to come up with {msg}?", ConsoleColor.Yellow); - Console.Write($"{msg}--> "); + var passwordNotConfirmed = true; + TextWrite.Print( + _destination is StringDestination.PassportType or StringDestination.Email + ? $"What is your {msg}?" + : $"Try to come up with {msg}?", ConsoleColor.Yellow); + Console.Write($"{msg}--> "); - output = Console.ReadLine() - ?.Trim() - .Replace(" ", ""); - if (string.IsNullOrEmpty(output)) - { - TextWrite.Print("Wrong data!", ConsoleColor.Red); + output = Console.ReadLine() + ?.Trim() + .Replace(" ", ""); + if (string.IsNullOrEmpty(output)) + { + TextWrite.Print("Wrong data!", ConsoleColor.Red); + continue; + } + switch (_destination) + { + case StringDestination.Password when output.Length < 6: + TextWrite.Print("Wrong password length!", ConsoleColor.Red); continue; - } - switch (_destination) - { - case StringDestination.Password when output.Length < 6: - TextWrite.Print("Wrong password length!", ConsoleColor.Red); - continue; - case StringDestination.Email when !StringValidator.IsEmailValid(output): - TextWrite.Print("This email is not valid!", ConsoleColor.Red); - continue; - } + case StringDestination.Email when !StringValidator.IsEmailValid(output): + TextWrite.Print("This email is not valid!", ConsoleColor.Red); + continue; + } - if (_destination == StringDestination.Password) + if (_destination == StringDestination.Password) + { + if (isNeedConfirmation) + break; + TextWrite.Print("You need to confirm password!", ConsoleColor.Yellow); + do { - if (isNeedConfirmation) - break; - TextWrite.Print("You need to confirm password!", ConsoleColor.Yellow); - do + Console.Write("Confirmation--> "); + var confirmationPassword = Console.ReadLine() + ?.Trim() + .Replace(" ", ""); + if (string.IsNullOrEmpty(output)) { - Console.Write("Confirmation--> "); - var confirmationPassword = Console.ReadLine() - ?.Trim() - .Replace(" ", ""); - if (string.IsNullOrEmpty(output)) - { - TextWrite.Print("Wrong data!", ConsoleColor.Red); - continue; - } - if (output == confirmationPassword) - { - TextWrite.Print("Password confirmed", ConsoleColor.Green); - passwordNotConfirmed = false; - } - else - TextWrite.Print("Passwords dont match!",ConsoleColor.Red); - } while (passwordNotConfirmed); - } - if (_destination == StringDestination.PassportType && StringValidator.IsStringContainsDigits(output)) - { - TextWrite.Print("You cannot enter nameType with digits!", ConsoleColor.Red); - continue; - } - break; + TextWrite.Print("Wrong data!", ConsoleColor.Red); + continue; + } + if (output == confirmationPassword) + { + TextWrite.Print("Password confirmed", ConsoleColor.Green); + passwordNotConfirmed = false; + } + else + TextWrite.Print("Passwords dont match!",ConsoleColor.Red); + } while (passwordNotConfirmed); + } + if (_destination == StringDestination.PassportType && StringValidator.IsStringContainsDigits(output)) + { + TextWrite.Print("You cannot enter nameType with digits!", ConsoleColor.Red); + continue; } - return output; + break; } + return output; } -} +} \ No newline at end of file diff --git a/Client/Services/TextWrite.cs b/Client/Services/TextWrite.cs index 0197dbc..cd60c41 100644 --- a/Client/Services/TextWrite.cs +++ b/Client/Services/TextWrite.cs @@ -1,14 +1,13 @@ using System; -namespace Client.Services +namespace Client.Services; + +public static class TextWrite { - public static class TextWrite + public static void Print(string msg, ConsoleColor color) { - public static void Print(string msg, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.WriteLine(msg); - Console.ResetColor(); - } + Console.ForegroundColor = color; + Console.WriteLine(msg); + Console.ResetColor(); } -} +} \ No newline at end of file diff --git a/Client/Validations/StringValidator.cs b/Client/Validations/StringValidator.cs index a1df9d2..83459f2 100644 --- a/Client/Validations/StringValidator.cs +++ b/Client/Validations/StringValidator.cs @@ -4,20 +4,19 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Client.Validations +namespace Client.Validations; + +public static class StringValidator { - public static class StringValidator + public static bool IsStringContainsDigits(string str) { - public static bool IsStringContainsDigits(string str) - { - var res = str.Any(ch => Char.IsDigit(ch)); - return res; - } - public static bool IsEmailValid(string email) - { - var emailPattern = "[.\\-_a-z0-9]+@([a-z0-9][\\-a-z0-9]+\\.)+[a-z]{2,6}"; - var match = Regex.Match(email,emailPattern,RegexOptions.IgnoreCase); - return match.Success; - } + var res = str.Any(ch => Char.IsDigit(ch)); + return res; + } + public static bool IsEmailValid(string email) + { + var emailPattern = "[.\\-_a-z0-9]+@([a-z0-9][\\-a-z0-9]+\\.)+[a-z]{2,6}"; + var match = Regex.Match(email,emailPattern,RegexOptions.IgnoreCase); + return match.Success; } -} +} \ No newline at end of file diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server.Bll/Exceptions/ExceptionTemplates.cs index c99b77e..8ceb06a 100644 --- a/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -5,10 +5,11 @@ internal static class ExceptionTemplates // GENERAL EXCEPTION MESSAGES internal const string Unknown = "Unknown error occured. Please try again"; internal const string NotAllowed = "You are not allowed to do this."; - + internal static string NotExists(string entity) => $"{entity} does not exist."; + + // ROOM EXCEPTIONS internal const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; - internal const string RoomNotExists = "Room does not exist."; internal const string RoomFull = "This room is full."; internal const string AlreadyInRoom = "You are already in room."; internal const string RoomNotFull = "Room is not full."; diff --git a/Server.Bll/Models/PlayerModel.cs b/Server.Bll/Models/PlayerModel.cs index e8c4150..3b51bcc 100644 --- a/Server.Bll/Models/PlayerModel.cs +++ b/Server.Bll/Models/PlayerModel.cs @@ -5,17 +5,13 @@ public sealed class PlayerModel { public PlayerModel( string id, - string accountId, int move) { Id = id; - AccountId = accountId; Move = move; } public string Id { get; init; } - - public string AccountId { get; set; } public int Move { get; set; } } \ No newline at end of file diff --git a/Server.Bll/Models/RoomModel.cs b/Server.Bll/Models/RoomModel.cs index 0803790..cbb5cec 100644 --- a/Server.Bll/Models/RoomModel.cs +++ b/Server.Bll/Models/RoomModel.cs @@ -1,4 +1,6 @@ -namespace Server.Bll.Models; +using System.Collections.Generic; + +namespace Server.Bll.Models; public sealed class RoomModel { @@ -16,11 +18,11 @@ public sealed class RoomModel /// Round, linked to this room /// public RoundModel? Round { get; set; } - + /// /// . /// - public PlayerModel? Player { get; set; } + public ICollection? Players { get; set; } /// /// Flag is this room is private diff --git a/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server.Bll/Services/Interfaces/ILongPollingService.cs index 460f2ab..45711bf 100644 --- a/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ b/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -1,10 +1,17 @@ +using System; using System.Threading.Tasks; namespace Server.Bll.Services.Interfaces; public interface ILongPollingService { + [Obsolete] Task CheckRoomState(string roomId); + + Task GetRoomUpdateTicksAsync(string roomId); + [Obsolete] Task CheckRoundState(string roundId); + + Task GetRoundUpdateTicksAsync(string roundId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server.Bll/Services/Interfaces/IRoomService.cs index 771d8e1..4bf5689 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server.Bll/Services/Interfaces/IRoomService.cs @@ -12,11 +12,11 @@ public interface IRoomService Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate); - Task> JoinAsync(string userId, bool isPrivate, string? roomCode = default); + Task> JoinAsync(string userId, string? roomCode = null); Task> GetAsync(string roomId); Task UpdateAsync(RoomModel room); - Task DeleteAsync(string userId, string roomId); + Task> DeleteAsync(string userId, string roomId); } \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs index 82a55a5..b623823 100644 --- a/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ b/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -8,5 +8,6 @@ namespace Server.Bll.Services.Interfaces; public interface IStatisticsService { Task GetAllStatistics(); + Task> GetPersonalStatistics(string userId); } \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server.Bll/Services/LongPollingService.cs index 67e9783..4fab204 100644 --- a/Server.Bll/Services/LongPollingService.cs +++ b/Server.Bll/Services/LongPollingService.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Server.Bll.Services.Interfaces; @@ -17,13 +16,36 @@ public LongPollingService(ServerContext serverContext) public Task CheckRoomState(string roomId) { - return _serverContext.Rooms.AnyAsync(room => room.Id.Equals(roomId)); + return _serverContext.Rooms + .AnyAsync(room => room.Id.Equals(roomId)); + } + + public async Task GetRoomUpdateTicksAsync(string roomId) + { + var room = await _serverContext.Rooms + .FindAsync(roomId); + + if (room is null) + { + return -1; + } + + return room.UpdateTicks; } public async Task CheckRoundState(string roundId) { - var thisRound = await _serverContext.Rounds.FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); + var round = await _serverContext.Rounds + .FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); + + return round?.IsFinished ?? false; + } + + public async Task GetRoundUpdateTicksAsync(string roundId) + { + var round = await _serverContext.Rounds + .FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); - return thisRound?.IsFinished ?? false; + return round?.UpdateTicks ?? -1; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoomService.cs b/Server.Bll/Services/RoomService.cs index a8d4067..a100e6a 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server.Bll/Services/RoomService.cs @@ -1,208 +1,147 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Mapster; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using OneOf; using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using Server.Data.Context; +using Server.Data.Entities; +using Server.Data.Extensions; namespace Server.Bll.Services; internal sealed class RoomService : IRoomService { + private static readonly Player BotPlayer = new() + { + Id = Guid.NewGuid().ToString(), + AccountId = SeedingExtension.BotId, + IsReady = true + }; + private readonly ServerContext _repository; - private readonly IRoundService _roundService; - public RoomService( - ServerContext repository, - IRoundService roundService) + public RoomService(ServerContext repository) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); - _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); } - public async Task> - CreateAsync(string userId, bool isPrivate = false, bool isTraining = false) + public async Task> CreateAsync( + string userId, bool isPrivate, bool isTraining) { - throw new NotImplementedException(); - // throw new NotImplementedException(); - // // var doesRoomExist = await _repository.RoomPlayersEnumerable - // // .AnyAsync(roomPlayers => roomPlayers.FirstPlayerId == userId - // // || roomPlayers.SecondPlayerId == userId); - // // - // // if (doesRoomExist) - // // { - // // return new CustomException(ExceptionTemplates.TwinkRoom); - // // } - // // - // // var room = new Room - // // { - // // IsPrivate = isPrivate, - // // Code = Guid.NewGuid().ToString("n")[..8], - // // IsFull = false, - // // CreationTimeTicks = DateTimeOffset.Now.Ticks, - // // Player = new Player - // // { - // // FirstPlayerId = userId, - // // Count = 1 - // // } - // // }; - // // - // // // await _rooms.AddAsync(room); - // // // await _repository.SaveChangesAsync(); - // // - // // // var newRoomPlayers = new RoomPlayers - // // // { - // // // RoomId = room.Id, - // // // FirstPlayerId = userId, - // // // PlayersCount = 1 - // // // }; - // // // - // // if (isTraining) - // // { - // // room.Player.SecondPlayerId = 0; - // // } - // // - // // _repository.Add(room); - // // - // // // room.RoomPlayers = newRoomPlayers; - // // // _rooms.Update(room); - // // // - // // await _repository.SaveChangesAsync(); - // // - // // return room.Adapt(); + var doesRoomExist = await _repository.Rooms + .Include(room => room.Players) + .AnyAsync(roomPlayers => roomPlayers.Players.Any(player => player.AccountId == userId)); + + if (doesRoomExist) + { + return new CustomException(ExceptionTemplates.TwinkRoom); + } + + var players = new List(2) + { + new() + { + Id = Guid.NewGuid().ToString(), + AccountId = userId, + IsReady = false, + } + }; + + var room = new Room + { + Id = Guid.NewGuid().ToString(), + IsPrivate = isPrivate, + Code = Guid.NewGuid().ToString("N")[..8], + IsFull = false, + CreationTimeTicks = DateTimeOffset.UtcNow.Ticks, + Players = players, + UpdateTicks = DateTimeOffset.UtcNow.Ticks + }; + + if (isTraining) + { + room.IsFull = true; + room.Players.Add(BotPlayer); + } + + _repository.Add(room); + + await _repository.SaveChangesAsync(); + + return room.Adapt(); } - public async Task> JoinAsync(int userId, bool isPrivate, string roomCode) + public async Task> JoinAsync(string userId, string? roomCode) { - throw new NotImplementedException(); - // var thisRoom = isPrivate - // ? await _rooms - // .Include(room => room.Player) - // .Where(room => !room.IsFull) - // .FirstOrDefaultAsync() - // : await _rooms - // .Include(room=>room.Player). - // FirstOrDefaultAsync(room => room.Code == roomCode); - // - // if (thisRoom is null) - // { - // return new CustomException(ExceptionTemplates.RoomNotExists); - // } - // - // if (thisRoom.Player.FirstPlayerId == userId || thisRoom.Player.SecondPlayerId == userId) - // { - // return new CustomException(ExceptionTemplates.AlreadyInRoom); - // } - // - // if (thisRoom.Player.FirstPlayerId is not 0 && thisRoom.Player.SecondPlayerId is not 0) - // { - // return new CustomException(ExceptionTemplates.RoomFull); - // } - // - // if (thisRoom.Player.FirstPlayerId is not 0) - // { - // thisRoom.Player.FirstPlayerId = userId; - // _rooms.Update(thisRoom); - // - // await _repository.SaveChangesAsync(); - // - // return thisRoom.Adapt(); - // } - // - // if (thisRoom.Player.SecondPlayerId is 0) - // { - // return new CustomException(ExceptionTemplates.Unknown); - // } - // - // thisRoom.Player.SecondPlayerId = userId; - // - // if (thisRoom.Player.FirstPlayerId is not 0 && thisRoom.Player.SecondPlayerId is not 0) - // { - // await _roundService.CreateAsync(userId, thisRoom.Id); - // } - // - // _rooms.Update(thisRoom); - // await _repository.SaveChangesAsync(); - // - // return thisRoom.Adapt(); - } + var oneOfRoom = string.IsNullOrEmpty(roomCode) ? await GetPublicAsync(userId) : await GetPrivateAsync(userId, roomCode); - public async Task> GetAsync(int roomId) - { - throw new NotImplementedException(); - // var room = await _rooms.FindAsync(roomId); - // - // return room is not null - // ? room.Adapt() - // : new CustomException(ExceptionTemplates.RoomNotExists); - } + if (oneOfRoom.IsT1) + { + return oneOfRoom.AsT1; + } - public Task> GetAsync(string roomId) - { - throw new NotImplementedException(); + var room = oneOfRoom.AsT0; + + var newPlayer = new Player + { + Id = Guid.NewGuid().ToString(), + AccountId = userId, + IsReady = false, + }; + + room.Players.Add(newPlayer); + room.UpdateTicks = DateTimeOffset.UtcNow.Ticks; + room.IsFull = room.Players.Count is 2; + + _repository.Rooms.Update(room); + + await _repository.SaveChangesAsync(); + + return room.Adapt(); } - public async Task UpdateAsync(RoomModel room) + public async Task> GetAsync(string roomId) { - throw new NotImplementedException(); - // var thisRoom = await _rooms.FindAsync(room.Id); - // - // if (thisRoom is null) - // { - // return StatusCodes.Status400BadRequest; - // } - // - // var updatedRoom = room.Adapt(); - // - // thisRoom.IsFull = updatedRoom.IsFull; - // thisRoom.IsPrivate = updatedRoom.IsPrivate; - // thisRoom.RoundId = updatedRoom.RoundId; - // - // if (!_repository.Entry(thisRoom).Properties.Any(x => x.IsModified)) - // { - // return StatusCodes.Status400BadRequest; - // } - // - // _repository.Update(thisRoom); - // - // return StatusCodes.Status200OK; + var room = await _repository.Rooms.FindAsync(roomId); + + if (room is null) + { + return new CustomException($"Room with id {roomId} does not exist"); + } + + return room.Adapt(); } - public Task DeleteAsync(string userId, string roomId) + public Task UpdateAsync(RoomModel room) { throw new NotImplementedException(); } - public async Task DeleteAsync(int userId, int roomId) + public async Task> DeleteAsync(string userId, string roomId) { - throw new NotImplementedException(); - // var thisRoom = await _rooms - // .Include(room=>room.Player) - // .FirstOrDefaultAsync(room => room.Id == roomId); - // - // if (thisRoom is null) - // { - // return StatusCodes.Status400BadRequest; - // } - // - // if (thisRoom.Player.FirstPlayerId != userId) - // { - // return StatusCodes.Status400BadRequest; - // } - // - // if (thisRoom.Player.SecondPlayerId != userId) - // { - // return StatusCodes.Status400BadRequest; - // } - // - // _rooms.Remove(thisRoom); - // - // await _repository.SaveChangesAsync(); - // - // return StatusCodes.Status200OK; + var room = await _repository.Rooms.FindAsync(roomId); + + if (room is null) + { + return new CustomException($"Room with id {roomId} does not exist"); + } + + if (room.Players.All(player => player.AccountId != userId)) + { + return new CustomException("You have no rights to delete a room"); + } + + _repository.Rooms.Remove(room); + + await _repository.SaveChangesAsync(); + + return StatusCodes.Status200OK; } public async Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate) @@ -239,9 +178,42 @@ public async Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutD return roomLength + roundsLength; } + + private async Task> GetPrivateAsync(string userId, string roomCode) + { + var room = await _repository.Rooms + .Include(room => room.Players) + .FirstOrDefaultAsync(room => room.Code == roomCode); - public Task> JoinAsync(string userId, bool isPrivate, string? roomCode = default) + if (room is null) + { + return new CustomException(ExceptionTemplates.NotExists(nameof(Room))); + } + + if (room.IsFull) + { + return new CustomException(ExceptionTemplates.RoomFull); + } + + if (room.Players.Any(player => player.AccountId == userId)) + { + return new CustomException(ExceptionTemplates.AlreadyInRoom); + } + + return room; + } + + private async Task> GetPublicAsync(string userId) { - throw new NotImplementedException(); + var room = await _repository.Rooms + .Include(room => room.Players) + .FirstOrDefaultAsync(room => !room.IsFull && !room.IsPrivate && room.Players.All(player => player.AccountId != userId)); + + if (room is null) + { + return new CustomException("There is no available free rooms"); + } + + return room; } } \ No newline at end of file diff --git a/Server.Bll/Services/RoundService.cs b/Server.Bll/Services/RoundService.cs index 0885c5f..2887843 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server.Bll/Services/RoundService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Mapster; @@ -13,7 +12,7 @@ namespace Server.Bll.Services; -internal sealed class RoundService : IRoundService +internal sealed class RoundService: IRoundService { private readonly ServerContext _serverContext; @@ -21,16 +20,16 @@ public RoundService(ServerContext serverContext) { _serverContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); } + public async Task> CreateAsync(string userId, string roomId) { - var playingRoom = await _serverContext - .Rooms + var playingRoom = await _serverContext.Rooms .Include(rooms => rooms.Players) .FirstOrDefaultAsync(room => room.Id.Equals(roomId)); if (playingRoom is null) { - return new CustomException(ExceptionTemplates.RoomNotExists); + return new CustomException(ExceptionTemplates.NotExists(nameof(Room))); } if (!playingRoom.IsFull) @@ -43,29 +42,25 @@ public async Task> CreateAsync(string userId, return new CustomException(ExceptionTemplates.NotAllowed); } + var updateTime = DateTimeOffset.UtcNow.Ticks; var newRound = new Round { Id = Guid.NewGuid().ToString(), RoomId = roomId, Room = playingRoom, - StartTimeTicks = DateTimeOffset.UtcNow.Ticks, + StartTimeTicks = updateTime, + UpdateTicks = updateTime, IsFinished = false }; - _serverContext.Rounds.Add(newRound); - - var players = new List(2) - { - new() - { - Id = Guid.NewGuid().ToString(), - AccountId = userId, - } - }; + playingRoom.UpdateTicks = updateTime; + + var players = playingRoom.Players; newRound.Players = players; - _serverContext.Rounds.Update(newRound); + _serverContext.Rounds.Add(newRound); + _serverContext.Rooms.Update(playingRoom); await _serverContext.SaveChangesAsync(); diff --git a/Server.Data/Entities/Account.cs b/Server.Data/Entities/Account.cs index d3f7d90..d6467e9 100644 --- a/Server.Data/Entities/Account.cs +++ b/Server.Data/Entities/Account.cs @@ -3,7 +3,7 @@ namespace Server.Data.Entities; -[Table("Accounts")] +[Table(nameof(Account))] public class Account { /// diff --git a/Server.Data/Entities/Player.cs b/Server.Data/Entities/Player.cs index e2e48a3..322c408 100644 --- a/Server.Data/Entities/Player.cs +++ b/Server.Data/Entities/Player.cs @@ -3,14 +3,14 @@ namespace Server.Data.Entities; -[Table("Players")] +[Table(nameof(Player))] public class Player { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public string Id { get; init; } - [ForeignKey("Account")] + [ForeignKey(nameof(Account))] public string AccountId { get; init; } public virtual Account Account { get; set; } diff --git a/Server.Data/Entities/Room.cs b/Server.Data/Entities/Room.cs index 1df8d95..ecf5ade 100644 --- a/Server.Data/Entities/Room.cs +++ b/Server.Data/Entities/Room.cs @@ -4,7 +4,7 @@ namespace Server.Data.Entities; -[Table("Rooms")] +[Table(nameof(Room))] public class Room { /// @@ -43,4 +43,9 @@ public class Room /// Creation date. After 5 minutes of inactivity will be deleted /// public long CreationTimeTicks { get; set; } + + /// + /// Last update time ticks. + /// + public long UpdateTicks { get; set; } } \ No newline at end of file diff --git a/Server.Data/Entities/Round.cs b/Server.Data/Entities/Round.cs index 18d315c..ed82581 100644 --- a/Server.Data/Entities/Round.cs +++ b/Server.Data/Entities/Round.cs @@ -4,14 +4,14 @@ namespace Server.Data.Entities; -[Table("Rounds")] +[Table(nameof(Round))] public class Round { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public string Id { get; init; } - [ForeignKey("Room")] + [ForeignKey(nameof(Room))] public string RoomId { get; set; } public virtual Room Room { get; set; } @@ -21,10 +21,12 @@ public class Round public virtual Account Winner { get; set; } public virtual Account Loser { get; set; } + + public bool IsFinished { get; init; } public long StartTimeTicks { get; set; } public long FinishTimeTicks { get; set; } - - public bool IsFinished { get; init; } + + public long UpdateTicks { get; set; } } \ No newline at end of file diff --git a/Server.Data/Entities/Statistics.cs b/Server.Data/Entities/Statistics.cs index 2d643c6..f9f54f2 100644 --- a/Server.Data/Entities/Statistics.cs +++ b/Server.Data/Entities/Statistics.cs @@ -4,14 +4,14 @@ namespace Server.Data.Entities; -[Table("Statistics")] +[Table(nameof(Statistics))] public class Statistics { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public string Id { get; init; } - [ForeignKey("Account")] + [ForeignKey(nameof(Account))] public string AccountId { get; set; } public virtual Account Account { get; set; } diff --git a/Server.Data/Extensions/SeedingExtension.cs b/Server.Data/Extensions/SeedingExtension.cs index 15a0707..8d0fbf7 100644 --- a/Server.Data/Extensions/SeedingExtension.cs +++ b/Server.Data/Extensions/SeedingExtension.cs @@ -8,26 +8,26 @@ namespace Server.Data.Extensions; public static class SeedingExtension { - private const string DefaultName = "bot"; + public const string BotId = "bot"; public static async Task EnsureBotCreated(this ServerContext context) { - if (await context.Accounts.ContainsAsync(new Account {Id = DefaultName})) + if (await context.Accounts.ContainsAsync(new Account {Id = BotId})) { return; } context.Add(new Account { - Id = DefaultName, - Login = DefaultName, + Id = BotId, + Login = BotId, Password = Guid.NewGuid().ToString() }); context.Add(new Statistics { - Id = DefaultName, - AccountId = DefaultName + Id = BotId, + AccountId = BotId }); await context.SaveChangesAsync(); diff --git a/Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs b/Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs similarity index 94% rename from Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs rename to Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs index 37b6c41..e688c16 100644 --- a/Server.Data/Migrations/20221013195804_InitialCommit.Designer.cs +++ b/Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs @@ -11,8 +11,8 @@ namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20221013195804_InitialCommit")] - partial class InitialCommit + [Migration("20221015144504_InitialMigration")] + partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -32,7 +32,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Accounts"); + b.ToTable("Account"); }); modelBuilder.Entity("Server.Data.Entities.Player", b => @@ -63,7 +63,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoundId"); - b.ToTable("Players"); + b.ToTable("Player"); }); modelBuilder.Entity("Server.Data.Entities.Room", b => @@ -83,9 +83,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsPrivate") .HasColumnType("INTEGER"); + b.Property("UpdateTicks") + .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.ToTable("Rooms"); + b.ToTable("Room"); }); modelBuilder.Entity("Server.Data.Entities.Round", b => @@ -108,6 +111,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("StartTimeTicks") .HasColumnType("INTEGER"); + b.Property("UpdateTicks") + .HasColumnType("INTEGER"); + b.Property("WinnerId") .HasColumnType("TEXT"); @@ -120,7 +126,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("WinnerId"); - b.ToTable("Rounds"); + b.ToTable("Round"); }); modelBuilder.Entity("Server.Data.Entities.Statistics", b => diff --git a/Server.Data/Migrations/20221013195804_InitialCommit.cs b/Server.Data/Migrations/20221015144504_InitialMigration.cs similarity index 74% rename from Server.Data/Migrations/20221013195804_InitialCommit.cs rename to Server.Data/Migrations/20221015144504_InitialMigration.cs index 4a9f4f8..afa7da6 100644 --- a/Server.Data/Migrations/20221013195804_InitialCommit.cs +++ b/Server.Data/Migrations/20221015144504_InitialMigration.cs @@ -5,12 +5,12 @@ namespace Server.Data.Migrations { - public partial class InitialCommit : Migration + public partial class InitialMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "Accounts", + name: "Account", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), @@ -19,22 +19,23 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_Accounts", x => x.Id); + table.PrimaryKey("PK_Account", x => x.Id); }); migrationBuilder.CreateTable( - name: "Rooms", + name: "Room", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), Code = table.Column(type: "TEXT", nullable: true), IsPrivate = table.Column(type: "INTEGER", nullable: false), IsFull = table.Column(type: "INTEGER", nullable: false), - CreationTimeTicks = table.Column(type: "INTEGER", nullable: false) + CreationTimeTicks = table.Column(type: "INTEGER", nullable: false), + UpdateTicks = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Rooms", x => x.Id); + table.PrimaryKey("PK_Room", x => x.Id); }); migrationBuilder.CreateTable( @@ -57,46 +58,47 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_Statistics", x => x.Id); table.ForeignKey( - name: "FK_Statistics_Accounts_AccountId", + name: "FK_Statistics_Account_AccountId", column: x => x.AccountId, - principalTable: "Accounts", + principalTable: "Account", principalColumn: "Id"); }); migrationBuilder.CreateTable( - name: "Rounds", + name: "Round", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), WinnerId = table.Column(type: "TEXT", nullable: true), LoserId = table.Column(type: "TEXT", nullable: true), + IsFinished = table.Column(type: "INTEGER", nullable: false), StartTimeTicks = table.Column(type: "INTEGER", nullable: false), FinishTimeTicks = table.Column(type: "INTEGER", nullable: false), - IsFinished = table.Column(type: "INTEGER", nullable: false) + UpdateTicks = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Rounds", x => x.Id); + table.PrimaryKey("PK_Round", x => x.Id); table.ForeignKey( - name: "FK_Rounds_Accounts_LoserId", + name: "FK_Round_Account_LoserId", column: x => x.LoserId, - principalTable: "Accounts", + principalTable: "Account", principalColumn: "Id"); table.ForeignKey( - name: "FK_Rounds_Accounts_WinnerId", + name: "FK_Round_Account_WinnerId", column: x => x.WinnerId, - principalTable: "Accounts", + principalTable: "Account", principalColumn: "Id"); table.ForeignKey( - name: "FK_Rounds_Rooms_RoomId", + name: "FK_Round_Room_RoomId", column: x => x.RoomId, - principalTable: "Rooms", + principalTable: "Room", principalColumn: "Id"); }); migrationBuilder.CreateTable( - name: "Players", + name: "Player", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), @@ -108,53 +110,53 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_Players", x => x.Id); + table.PrimaryKey("PK_Player", x => x.Id); table.ForeignKey( - name: "FK_Players_Accounts_AccountId", + name: "FK_Player_Account_AccountId", column: x => x.AccountId, - principalTable: "Accounts", + principalTable: "Account", principalColumn: "Id"); table.ForeignKey( - name: "FK_Players_Rooms_RoomId", + name: "FK_Player_Room_RoomId", column: x => x.RoomId, - principalTable: "Rooms", + principalTable: "Room", principalColumn: "Id"); table.ForeignKey( - name: "FK_Players_Rounds_RoundId", + name: "FK_Player_Round_RoundId", column: x => x.RoundId, - principalTable: "Rounds", + principalTable: "Round", principalColumn: "Id"); }); migrationBuilder.CreateIndex( - name: "IX_Players_AccountId", - table: "Players", + name: "IX_Player_AccountId", + table: "Player", column: "AccountId"); migrationBuilder.CreateIndex( - name: "IX_Players_RoomId", - table: "Players", + name: "IX_Player_RoomId", + table: "Player", column: "RoomId"); migrationBuilder.CreateIndex( - name: "IX_Players_RoundId", - table: "Players", + name: "IX_Player_RoundId", + table: "Player", column: "RoundId"); migrationBuilder.CreateIndex( - name: "IX_Rounds_LoserId", - table: "Rounds", + name: "IX_Round_LoserId", + table: "Round", column: "LoserId"); migrationBuilder.CreateIndex( - name: "IX_Rounds_RoomId", - table: "Rounds", + name: "IX_Round_RoomId", + table: "Round", column: "RoomId", unique: true); migrationBuilder.CreateIndex( - name: "IX_Rounds_WinnerId", - table: "Rounds", + name: "IX_Round_WinnerId", + table: "Round", column: "WinnerId"); migrationBuilder.CreateIndex( @@ -167,19 +169,19 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Players"); + name: "Player"); migrationBuilder.DropTable( name: "Statistics"); migrationBuilder.DropTable( - name: "Rounds"); + name: "Round"); migrationBuilder.DropTable( - name: "Accounts"); + name: "Account"); migrationBuilder.DropTable( - name: "Rooms"); + name: "Room"); } } } diff --git a/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server.Data/Migrations/ServerContextModelSnapshot.cs index 0905b3a..9bbc314 100644 --- a/Server.Data/Migrations/ServerContextModelSnapshot.cs +++ b/Server.Data/Migrations/ServerContextModelSnapshot.cs @@ -30,7 +30,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Accounts"); + b.ToTable("Account"); }); modelBuilder.Entity("Server.Data.Entities.Player", b => @@ -61,7 +61,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoundId"); - b.ToTable("Players"); + b.ToTable("Player"); }); modelBuilder.Entity("Server.Data.Entities.Room", b => @@ -81,9 +81,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsPrivate") .HasColumnType("INTEGER"); + b.Property("UpdateTicks") + .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.ToTable("Rooms"); + b.ToTable("Room"); }); modelBuilder.Entity("Server.Data.Entities.Round", b => @@ -106,6 +109,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("StartTimeTicks") .HasColumnType("INTEGER"); + b.Property("UpdateTicks") + .HasColumnType("INTEGER"); + b.Property("WinnerId") .HasColumnType("TEXT"); @@ -118,7 +124,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("WinnerId"); - b.ToTable("Rounds"); + b.ToTable("Round"); }); modelBuilder.Entity("Server.Data.Entities.Statistics", b => diff --git a/Server.Data/Server.Data.csproj b/Server.Data/Server.Data.csproj index 6797ec4..338fef8 100644 --- a/Server.Data/Server.Data.csproj +++ b/Server.Data/Server.Data.csproj @@ -12,8 +12,4 @@ - - - - diff --git a/Server.Host/Controllers/AccountController.cs b/Server.Host/Controllers/AccountController.cs index 27906d5..502faa2 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server.Host/Controllers/AccountController.cs @@ -15,7 +15,7 @@ namespace Server.Host.Controllers; [Route ("api/[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] -public sealed class AccountController : ControllerBase +public sealed class AccountController: ControllerBase { private readonly IAuthService _authService; diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server.Host/Controllers/LongPollingController.cs index 59cfa96..324284b 100644 --- a/Server.Host/Controllers/LongPollingController.cs +++ b/Server.Host/Controllers/LongPollingController.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; @@ -16,22 +15,22 @@ public sealed class LongPollingController : ControllerBase { private readonly ILongPollingService _longPollingService; - public LongPollingController(ILongPollingService longPollingService, ILogger logger) + public LongPollingController(ILongPollingService longPollingService) { _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } - [HttpGet("room")] - [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] - public Task CheckRoomState([FromQuery] string roomId) + [HttpGet("Room/{roomId}/status")] + [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] + public Task CheckRoomUpdateTicksAsync(string roomId) { - return _longPollingService.CheckRoomState(roomId); + return _longPollingService.GetRoomUpdateTicksAsync(roomId); } - [HttpGet("round")] - [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] - public Task CheckRoundState(string roundId) + [HttpGet("Round/{roundId}/status")] + [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] + public Task CheckRoundUpdateTicksAsync(string roundId) { - return _longPollingService.CheckRoundState(roundId); + return _longPollingService.GetRoundUpdateTicksAsync(roundId); } } \ No newline at end of file diff --git a/Server.Host/Controllers/RoomController.cs b/Server.Host/Controllers/RoomController.cs index 400584b..2f05b7e 100644 --- a/Server.Host/Controllers/RoomController.cs +++ b/Server.Host/Controllers/RoomController.cs @@ -15,7 +15,7 @@ namespace Server.Host.Controllers; [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public sealed class RoomController : ControllerBase +public sealed class RoomController: ControllerBase { private readonly IRoomService _roomService; @@ -46,18 +46,19 @@ public async Task CreateAsync( [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task JoinPublicAsync() { - var result = await _roomService.JoinAsync(UserId, true); + var result = await _roomService.JoinAsync(UserId); return result.Match( roomModel => Ok(roomModel), exception => BadRequest(exception)); } + [HttpPost("join/private")] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task JoinPrivateAsync([FromQuery] string roomCode) { - var result = await _roomService.JoinAsync(UserId, false, roomCode); + var result = await _roomService.JoinAsync(UserId, roomCode); return result.Match( roomModel => Ok(roomModel), @@ -85,10 +86,8 @@ public async Task DeleteAsync([FromQuery] string roomId) { var deleteResponse = await _roomService.DeleteAsync(UserId, roomId); - return deleteResponse switch - { - StatusCodes.Status200OK => Ok(), - _ => BadRequest() - }; + return deleteResponse.Match( + _ => Ok(), + exception => BadRequest(exception)); } } \ No newline at end of file diff --git a/Server.Host/Controllers/RoundController.cs b/Server.Host/Controllers/RoundController.cs index 15fa16f..7a35366 100644 --- a/Server.Host/Controllers/RoundController.cs +++ b/Server.Host/Controllers/RoundController.cs @@ -17,7 +17,7 @@ namespace Server.Host.Controllers; [Route ("api/[controller]")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public sealed class RoundController:ControllerBase +public sealed class RoundController: ControllerBase { private readonly IRoundService _roundService; diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server.Host/Controllers/StatisticsController.cs index 5c6d236..b86d5b9 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server.Host/Controllers/StatisticsController.cs @@ -16,7 +16,7 @@ namespace Server.Host.Controllers; [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public sealed class StatisticsController : ControllerBase +public sealed class StatisticsController: ControllerBase { private readonly IStatisticsService _statisticsService; diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server.Host/Extensions/LoggingMiddleware.cs index a35ba05..f2e127f 100644 --- a/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server.Host/Extensions/LoggingMiddleware.cs @@ -123,6 +123,8 @@ private static async Task BuildLogAsync(HttpContext context) return string.Create(length, (context, requestBody), (span, tuple) => { var index = 0; + + var (thisContext, thisRequestBody) = tuple; var tempString = "Request information:\n"; tempString.CopyTo(span[index..]); @@ -132,8 +134,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Scheme.CopyTo(span[index..]); - index += context.Request.Scheme.Length; + thisContext.Request.Scheme.CopyTo(span[index..]); + index += thisContext.Request.Scheme.Length; "\n".CopyTo(span[index++..]); @@ -141,8 +143,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.ContentType?.CopyTo(span[index..]); - index += context.Request.ContentType?.Length ?? 0; + thisContext.Request.ContentType?.CopyTo(span[index..]); + index += thisContext.Request.ContentType?.Length ?? 0; "\n".CopyTo(span[index++..]); @@ -150,8 +152,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Host.Host.CopyTo(span[index..]); - index += context.Request.Host.Host.Length; + thisContext.Request.Host.Host.CopyTo(span[index..]); + index += thisContext.Request.Host.Host.Length; "\n".CopyTo(span[index++..]); @@ -159,8 +161,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.Path.Value?.CopyTo(span[index..]); - index += context.Request.Path.Value?.Length ?? 0; + thisContext.Request.Path.Value?.CopyTo(span[index..]); + index += thisContext.Request.Path.Value?.Length ?? 0; "\n".CopyTo(span[index++..]); @@ -168,8 +170,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - context.Request.QueryString.Value?.CopyTo(span[index..]); - index += context.Request.QueryString.Value?.Length ?? 0; + thisContext.Request.QueryString.Value?.CopyTo(span[index..]); + index += thisContext.Request.QueryString.Value?.Length ?? 0; "\n".CopyTo(span[index++..]); @@ -177,8 +179,8 @@ private static async Task BuildLogAsync(HttpContext context) tempString.CopyTo(span[index..]); index += tempString.Length; - tuple.requestBody.CopyTo(span[index..]); - index += tuple.requestBody.Length; + thisRequestBody.CopyTo(span[index..]); + index += thisRequestBody.Length; "\n".CopyTo(span[index..]); }); diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server.Host/Extensions/SwaggerExtension.cs index c2b5c6f..08f0218 100644 --- a/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server.Host/Extensions/SwaggerExtension.cs @@ -1,4 +1,6 @@ using System; +using System.Reflection; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -16,40 +18,43 @@ public static class SwaggerExtension /// Service collection. public static IServiceCollection AddSwagger(this IServiceCollection services) { - if (services is null) throw new ArgumentNullException(nameof(services)); + ArgumentNullException.ThrowIfNull(services); services.AddSwaggerGen(options => { - // options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.XML"); - options.SwaggerDoc("v1", new OpenApiInfo { Title = "RPC Host", Version = "v1" }); + // options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); + options.SwaggerDoc("v1", new OpenApiInfo + { + Title = "RPC Host", + Version = "v1" + }); - options.AddSecurityRequirement( - new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = "Bearer", - Type = ReferenceType.SecurityScheme - }, - }, - Array.Empty() - } - }); + var jwtSecurityScheme = new OpenApiSecurityScheme + { + BearerFormat = "JWT", + Name = "JWT Authentication", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = JwtBearerDefaults.AuthenticationScheme, + Description = "Put **_ONLY_** your JWT Bearer token on text box below!", - options.AddSecurityDefinition( - "Bearer", - new OpenApiSecurityScheme - { - Type = SecuritySchemeType.ApiKey, - In = ParameterLocation.Header, - Scheme = "Bearer", - Name = "Authorization", - Description = "JWT token", - BearerFormat = "JWT" - }); + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + + options.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + jwtSecurityScheme, + Array.Empty() + } + }); + }); return services; From 233788503ecab30990c074c413ed976e2496ae25 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 16 Oct 2022 00:10:43 +0300 Subject: [PATCH 47/56] Refactored everything --- Client/Client - Backup.csproj | 17 -- Client/Client.Account/Client.Account.csproj | 13 ++ Client/Client.Account/Enums/MenuTypes.cs | 9 + .../Extensions/EnumExtensions.cs | 39 +++++ .../Extensions/ServiceCollectionExtensions.cs | 18 ++ Client/Client.Account/Menus/AccountMenu.cs | 154 ++++++++++++++++++ Client/Client.Account/Menus/IAccountMenu.cs | 8 + .../Client.Account/Services/AccountService.cs | 104 ++++++++++++ .../Services/IAccountService.cs | 25 +++ Client/Client.Account/Services/IUser.cs | 12 ++ Client/Client.Account/Services/User.cs | 25 +++ Client/Client.Host/Client.Host.csproj | 19 +++ Client/{ => Client.Host}/ClientAppEmulator.cs | 12 +- .../Client.Host/Extensions/JsonExtension.cs | 38 +++++ Client/Client.Host/Menus/AccountMenu.cs | 101 ++++++++++++ Client/Client.Host/Menus/IAccountMenu.cs | 13 ++ Client/{ => Client.Host}/Menus/IMainMenu.cs | 2 +- Client/{ => Client.Host}/Menus/MainMenu.cs | 12 +- Client/Client.Host/Menus/StartMenu.cs | 121 ++++++++++++++ Client/Client.Host/Models/Account.cs | 13 ++ Client/Client.Host/Models/ErrorModel.cs | 11 ++ .../Models/Interfaces/IAccount.cs | 2 +- .../Models/Interfaces/IOverallStatistics.cs | 2 +- .../Models/Interfaces/IRoom.cs | 12 +- .../Models/Interfaces/IRound.cs | 16 +- .../Models/Interfaces/IStatistics.cs | 2 +- .../Models/Interfaces/StatisticsDto.cs | 8 +- Client/{ => Client.Host}/Models/Room.cs | 16 +- Client/{ => Client.Host}/Models/Round.cs | 22 +-- Client/{ => Client.Host}/Models/Statistics.cs | 4 +- Client/{ => Client.Host}/Models/TokenModel.cs | 8 +- Client/Client.Host/Program.cs | 56 +++++++ .../RequestProcessor/IRequestHandler.cs | 4 +- .../RequestProcessor/IRequestPerformer.cs | 4 +- .../RequestProcessor/Impl/RequestHandler.cs | 9 +- .../RequestProcessor/Impl/RequestPerformer.cs | 4 +- .../RequestModels/IRequestOptions.cs | 8 +- .../RequestModels/IResponse.cs | 2 +- .../RequestModels/Impl/RequestOptions.cs | 2 +- .../RequestModels/Impl/Response.cs | 2 +- .../RequestModels/RequestMethod.cs | 2 +- Client/Client.Host/Services/RoomService.cs | 59 +++++++ .../Client.Host/Services/StatisticsService.cs | 74 +++++++++ .../Client.StartMenu/Client.StartMenu.csproj | 15 ++ Client/Client.StartMenu/Enums/MenuTypes.cs | 9 + .../Extensions/EnumExtensions.cs | 39 +++++ .../Extensions/ServiceCollectionExtensions.cs | 16 ++ Client/Client.StartMenu/Menus/IStartMenu.cs | 6 + Client/Client.StartMenu/Menus/StartMenu.cs | 90 ++++++++++ .../Client.Statistics.csproj | 14 ++ Client/Client.Statistics/Enums/MenuTypes.cs | 9 + .../Extensions/EnumExtensions.cs | 39 +++++ .../Extensions/ServiceCollectionExtensions.cs | 19 +++ .../Menus/IStatisticsMenu.cs | 6 + .../Client.Statistics/Menus/StatisticsMenu.cs | 153 +++++++++++++++++ .../Services/IStatisticsService.cs | 14 ++ .../Services/StatisticsService.cs | 34 ++++ Client/Client.csproj | 16 -- Client/Extensions/JsonExtension.cs | 38 ----- Client/Menus/AccountMenu.cs | 101 ------------ Client/Menus/IAccountMenu.cs | 13 -- Client/Menus/StartMenu.cs | 121 -------------- Client/Models/Account.cs | 13 -- Client/Models/ErrorModel.cs | 11 -- Client/Program.cs | 29 ---- Client/Services/RoomService.cs | 62 ------- Client/Services/StatisticsService.cs | 74 --------- Client/Services/StringPlaceholder.cs | 82 ---------- Client/Services/TextWrite.cs | 13 -- Client/Validations/StringValidator.cs | 22 --- .../RockPaperScissors.Common/Client/Client.cs | 144 ++++++++++++++++ .../Client/IClient.cs | 25 +++ .../CustomException.cs | 25 +++ .../Enums}/StringDestination.cs | 2 +- .../Extensions/QueryBuilder.cs | 133 +++++++++++++++ .../Extensions/TextWriteExtensions.cs | 97 +++++++++++ .../Models}/AccountDto.cs | 5 +- .../Models}/StatisticsDto.cs | 6 +- .../Requests/LoginRequest.cs | 13 +- .../Requests/RegisterRequest.cs | 9 +- .../Responses/AllStatisticsResponse.cs | 15 ++ .../Responses/LoginResponse.cs | 18 ++ .../Responses/PersonalStatisticsResponse.cs | 60 +++++++ .../RockPaperScissors.Common.csproj | 13 ++ RockPaperScissorsGame.sln | 91 +++++++---- Server.Bll/Exceptions/CustomException.cs | 15 -- .../Services/Interfaces/IStatisticsService.cs | 13 -- Server.Host/Account | 1 - .../Contracts/ViewModels/AccountViewModel.cs | 8 - .../Server.Authentication}/AuthOptions.cs | 0 .../Exceptions/UserException.cs | 0 .../Exceptions/UserExceptionsTemplates.cs | 0 .../Extensions/AuthenticationExtension.cs | 0 .../Server.Authentication}/HashingBase64.cs | 0 .../Models/AccountOutputModel.cs | 0 .../Server.Authentication}/Models/Roles.cs | 0 .../Server.Authentication.csproj | 0 .../Services/AttemptValidationService.cs | 0 .../Services/AuthService.cs | 0 .../Services/IAuthService.cs | 0 .../Exceptions/ExceptionTemplates.cs | 0 .../Extensions/MappingExtensions.cs | 14 ++ .../Extensions/ServiceCollectionExtensions.cs | 13 +- .../HostedServices/CleanerHostedService.cs | 0 .../Server.Bll}/Models/AccountModel.cs | 0 .../Server.Bll}/Models/PlayerModel.cs | 0 .../Server.Bll}/Models/RoomModel.cs | 0 .../Server.Bll}/Models/RoundModel.cs | 0 .../Models/ShortStatisticsModel.cs | 8 +- .../Server.Bll}/Models/StatisticsModel.cs | 5 - .../Server.Bll}/Server.Bll.csproj | 1 + .../Interfaces/ILongPollingService.cs | 0 .../Services/Interfaces/IRoomService.cs | 2 +- .../Services/Interfaces/IRoundService.cs | 2 +- .../Services/Interfaces/IStatisticsService.cs | 13 ++ .../Services/LongPollingService.cs | 0 .../Server.Bll}/Services/RoomService.cs | 1 + .../Server.Bll}/Services/RoundService.cs | 3 +- .../Server.Bll}/Services/StatisticsService.cs | 21 ++- .../Server.Data}/Context/ServerContext.cs | 4 + .../Server.Data}/Entities/Account.cs | 0 .../Server.Data}/Entities/Player.cs | 0 .../Server.Data}/Entities/Room.cs | 0 .../Server.Data}/Entities/Round.cs | 0 .../Server.Data}/Entities/Statistics.cs | 0 .../Extensions/DatabaseExtension.cs | 0 .../Extensions/SeedingExtension.cs | 0 ...0221015192454_InitialMigration.Designer.cs | 2 +- .../20221015192454_InitialMigration.cs | 0 .../Migrations/ServerContextModelSnapshot.cs | 0 .../Server.Data}/Server.Data.csproj | 0 .../Controllers/AccountController.cs | 11 +- .../Controllers/LongPollingController.cs | 0 .../Controllers/RoomController.cs | 0 .../Controllers/RoundController.cs | 0 .../Controllers/StatisticsController.cs | 6 +- .../Extensions/LoggingMiddleware.cs | 0 .../Extensions/SwaggerExtension.cs | 0 .../Server.Host}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../Server.Host}/Server.Host.csproj | 3 +- .../Server.Host}/Startup.cs | 0 .../Server.Host}/appsettings.json | 0 143 files changed, 2149 insertions(+), 795 deletions(-) delete mode 100644 Client/Client - Backup.csproj create mode 100644 Client/Client.Account/Client.Account.csproj create mode 100644 Client/Client.Account/Enums/MenuTypes.cs create mode 100644 Client/Client.Account/Extensions/EnumExtensions.cs create mode 100644 Client/Client.Account/Extensions/ServiceCollectionExtensions.cs create mode 100644 Client/Client.Account/Menus/AccountMenu.cs create mode 100644 Client/Client.Account/Menus/IAccountMenu.cs create mode 100644 Client/Client.Account/Services/AccountService.cs create mode 100644 Client/Client.Account/Services/IAccountService.cs create mode 100644 Client/Client.Account/Services/IUser.cs create mode 100644 Client/Client.Account/Services/User.cs create mode 100644 Client/Client.Host/Client.Host.csproj rename Client/{ => Client.Host}/ClientAppEmulator.cs (99%) create mode 100644 Client/Client.Host/Extensions/JsonExtension.cs create mode 100644 Client/Client.Host/Menus/AccountMenu.cs create mode 100644 Client/Client.Host/Menus/IAccountMenu.cs rename Client/{ => Client.Host}/Menus/IMainMenu.cs (75%) rename Client/{ => Client.Host}/Menus/MainMenu.cs (94%) create mode 100644 Client/Client.Host/Menus/StartMenu.cs create mode 100644 Client/Client.Host/Models/Account.cs create mode 100644 Client/Client.Host/Models/ErrorModel.cs rename Client/{ => Client.Host}/Models/Interfaces/IAccount.cs (71%) rename Client/{ => Client.Host}/Models/Interfaces/IOverallStatistics.cs (70%) rename Client/{ => Client.Host}/Models/Interfaces/IRoom.cs (58%) rename Client/{ => Client.Host}/Models/Interfaces/IRound.cs (61%) rename Client/{ => Client.Host}/Models/Interfaces/IStatistics.cs (96%) rename Client/{ => Client.Host}/Models/Interfaces/StatisticsDto.cs (59%) rename Client/{ => Client.Host}/Models/Room.cs (56%) rename Client/{ => Client.Host}/Models/Round.cs (59%) rename Client/{ => Client.Host}/Models/Statistics.cs (96%) rename Client/{ => Client.Host}/Models/TokenModel.cs (55%) create mode 100644 Client/Client.Host/Program.cs rename Client/{ => Client.Host}/Services/RequestProcessor/IRequestHandler.cs (56%) rename Client/{ => Client.Host}/Services/RequestProcessor/IRequestPerformer.cs (57%) rename Client/{ => Client.Host}/Services/RequestProcessor/Impl/RequestHandler.cs (93%) rename Client/{ => Client.Host}/Services/RequestProcessor/Impl/RequestPerformer.cs (87%) rename Client/{ => Client.Host}/Services/RequestProcessor/RequestModels/IRequestOptions.cs (76%) rename Client/{ => Client.Host}/Services/RequestProcessor/RequestModels/IResponse.cs (65%) rename Client/{ => Client.Host}/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs (87%) rename Client/{ => Client.Host}/Services/RequestProcessor/RequestModels/Impl/Response.cs (80%) rename Client/{ => Client.Host}/Services/RequestProcessor/RequestModels/RequestMethod.cs (64%) create mode 100644 Client/Client.Host/Services/RoomService.cs create mode 100644 Client/Client.Host/Services/StatisticsService.cs create mode 100644 Client/Client.StartMenu/Client.StartMenu.csproj create mode 100644 Client/Client.StartMenu/Enums/MenuTypes.cs create mode 100644 Client/Client.StartMenu/Extensions/EnumExtensions.cs create mode 100644 Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs create mode 100644 Client/Client.StartMenu/Menus/IStartMenu.cs create mode 100644 Client/Client.StartMenu/Menus/StartMenu.cs create mode 100644 Client/Client.Statistics/Client.Statistics.csproj create mode 100644 Client/Client.Statistics/Enums/MenuTypes.cs create mode 100644 Client/Client.Statistics/Extensions/EnumExtensions.cs create mode 100644 Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs create mode 100644 Client/Client.Statistics/Menus/IStatisticsMenu.cs create mode 100644 Client/Client.Statistics/Menus/StatisticsMenu.cs create mode 100644 Client/Client.Statistics/Services/IStatisticsService.cs create mode 100644 Client/Client.Statistics/Services/StatisticsService.cs delete mode 100644 Client/Client.csproj delete mode 100644 Client/Extensions/JsonExtension.cs delete mode 100644 Client/Menus/AccountMenu.cs delete mode 100644 Client/Menus/IAccountMenu.cs delete mode 100644 Client/Menus/StartMenu.cs delete mode 100644 Client/Models/Account.cs delete mode 100644 Client/Models/ErrorModel.cs delete mode 100644 Client/Program.cs delete mode 100644 Client/Services/RoomService.cs delete mode 100644 Client/Services/StatisticsService.cs delete mode 100644 Client/Services/StringPlaceholder.cs delete mode 100644 Client/Services/TextWrite.cs delete mode 100644 Client/Validations/StringValidator.cs create mode 100644 Common/RockPaperScissors.Common/Client/Client.cs create mode 100644 Common/RockPaperScissors.Common/Client/IClient.cs create mode 100644 Common/RockPaperScissors.Common/CustomException.cs rename {Client/Models => Common/RockPaperScissors.Common/Enums}/StringDestination.cs (75%) create mode 100644 Common/RockPaperScissors.Common/Extensions/QueryBuilder.cs create mode 100644 Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs rename {Server.Host/Contracts => Common/RockPaperScissors.Common/Models}/AccountDto.cs (70%) rename {Server.Host/Contracts => Common/RockPaperScissors.Common/Models}/StatisticsDto.cs (60%) rename {Server.Host/Contracts => Common/RockPaperScissors.Common}/Requests/LoginRequest.cs (59%) rename {Server.Host/Contracts => Common/RockPaperScissors.Common}/Requests/RegisterRequest.cs (52%) create mode 100644 Common/RockPaperScissors.Common/Responses/AllStatisticsResponse.cs create mode 100644 Common/RockPaperScissors.Common/Responses/LoginResponse.cs create mode 100644 Common/RockPaperScissors.Common/Responses/PersonalStatisticsResponse.cs create mode 100644 Common/RockPaperScissors.Common/RockPaperScissors.Common.csproj delete mode 100644 Server.Bll/Exceptions/CustomException.cs delete mode 100644 Server.Bll/Services/Interfaces/IStatisticsService.cs delete mode 100644 Server.Host/Account delete mode 100644 Server.Host/Contracts/ViewModels/AccountViewModel.cs rename {Server.Authentication => Server/Server.Authentication}/AuthOptions.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Exceptions/UserException.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Exceptions/UserExceptionsTemplates.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Extensions/AuthenticationExtension.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/HashingBase64.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Models/AccountOutputModel.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Models/Roles.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Server.Authentication.csproj (100%) rename {Server.Authentication => Server/Server.Authentication}/Services/AttemptValidationService.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Services/AuthService.cs (100%) rename {Server.Authentication => Server/Server.Authentication}/Services/IAuthService.cs (100%) rename {Server.Bll => Server/Server.Bll}/Exceptions/ExceptionTemplates.cs (100%) create mode 100644 Server/Server.Bll/Extensions/MappingExtensions.cs rename {Server.Bll => Server/Server.Bll}/Extensions/ServiceCollectionExtensions.cs (58%) rename {Server.Bll => Server/Server.Bll}/HostedServices/CleanerHostedService.cs (100%) rename {Server.Bll => Server/Server.Bll}/Models/AccountModel.cs (100%) rename {Server.Bll => Server/Server.Bll}/Models/PlayerModel.cs (100%) rename {Server.Bll => Server/Server.Bll}/Models/RoomModel.cs (100%) rename {Server.Bll => Server/Server.Bll}/Models/RoundModel.cs (100%) rename {Server.Bll => Server/Server.Bll}/Models/ShortStatisticsModel.cs (51%) rename {Server.Bll => Server/Server.Bll}/Models/StatisticsModel.cs (90%) rename {Server.Bll => Server/Server.Bll}/Server.Bll.csproj (87%) rename {Server.Bll => Server/Server.Bll}/Services/Interfaces/ILongPollingService.cs (100%) rename {Server.Bll => Server/Server.Bll}/Services/Interfaces/IRoomService.cs (95%) rename {Server.Bll => Server/Server.Bll}/Services/Interfaces/IRoundService.cs (92%) create mode 100644 Server/Server.Bll/Services/Interfaces/IStatisticsService.cs rename {Server.Bll => Server/Server.Bll}/Services/LongPollingService.cs (100%) rename {Server.Bll => Server/Server.Bll}/Services/RoomService.cs (99%) rename {Server.Bll => Server/Server.Bll}/Services/RoundService.cs (99%) rename {Server.Bll => Server/Server.Bll}/Services/StatisticsService.cs (63%) rename {Server.Data => Server/Server.Data}/Context/ServerContext.cs (81%) rename {Server.Data => Server/Server.Data}/Entities/Account.cs (100%) rename {Server.Data => Server/Server.Data}/Entities/Player.cs (100%) rename {Server.Data => Server/Server.Data}/Entities/Room.cs (100%) rename {Server.Data => Server/Server.Data}/Entities/Round.cs (100%) rename {Server.Data => Server/Server.Data}/Entities/Statistics.cs (100%) rename {Server.Data => Server/Server.Data}/Extensions/DatabaseExtension.cs (100%) rename {Server.Data => Server/Server.Data}/Extensions/SeedingExtension.cs (100%) rename Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs => Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs (99%) rename Server.Data/Migrations/20221015144504_InitialMigration.cs => Server/Server.Data/Migrations/20221015192454_InitialMigration.cs (100%) rename {Server.Data => Server/Server.Data}/Migrations/ServerContextModelSnapshot.cs (100%) rename {Server.Data => Server/Server.Data}/Server.Data.csproj (100%) rename {Server.Host => Server/Server.Host}/Controllers/AccountController.cs (85%) rename {Server.Host => Server/Server.Host}/Controllers/LongPollingController.cs (100%) rename {Server.Host => Server/Server.Host}/Controllers/RoomController.cs (100%) rename {Server.Host => Server/Server.Host}/Controllers/RoundController.cs (100%) rename {Server.Host => Server/Server.Host}/Controllers/StatisticsController.cs (90%) rename {Server.Host => Server/Server.Host}/Extensions/LoggingMiddleware.cs (100%) rename {Server.Host => Server/Server.Host}/Extensions/SwaggerExtension.cs (100%) rename {Server.Host => Server/Server.Host}/Program.cs (100%) rename {Server.Host => Server/Server.Host}/Properties/launchSettings.json (100%) rename {Server.Host => Server/Server.Host}/Server.Host.csproj (91%) rename {Server.Host => Server/Server.Host}/Startup.cs (100%) rename {Server.Host => Server/Server.Host}/appsettings.json (100%) diff --git a/Client/Client - Backup.csproj b/Client/Client - Backup.csproj deleted file mode 100644 index 9405ab1..0000000 --- a/Client/Client - Backup.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - net5.0 - - - - - - - - - - - - diff --git a/Client/Client.Account/Client.Account.csproj b/Client/Client.Account/Client.Account.csproj new file mode 100644 index 0000000..d0005cc --- /dev/null +++ b/Client/Client.Account/Client.Account.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Client/Client.Account/Enums/MenuTypes.cs b/Client/Client.Account/Enums/MenuTypes.cs new file mode 100644 index 0000000..614a7ff --- /dev/null +++ b/Client/Client.Account/Enums/MenuTypes.cs @@ -0,0 +1,9 @@ +namespace Client.Account.Enums; + +internal enum MenuTypes +{ + Unknown = 0, + SignUp = 1, + Login = 2, + Back = 3, +} \ No newline at end of file diff --git a/Client/Client.Account/Extensions/EnumExtensions.cs b/Client/Client.Account/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..03c3339 --- /dev/null +++ b/Client/Client.Account/Extensions/EnumExtensions.cs @@ -0,0 +1,39 @@ +using Client.Account.Enums; + +namespace Client.Account.Extensions; + +internal static class EnumExtensions +{ + internal static string GetDisplayName(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Back => "Back", + MenuTypes.Login => "Login", + MenuTypes.SignUp => "Sign Up", + _ => "Unknown", + }; + } + + internal static MenuTypes TryGetMenuType(this string? stringInput) + { + return stringInput switch + { + "3" => MenuTypes.Back, + "2" => MenuTypes.Login, + "1" => MenuTypes.SignUp, + _ => MenuTypes.Unknown, + }; + } + + internal static int GetValue(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Back => 3, + MenuTypes.Login => 2, + MenuTypes.SignUp => 1, + _ => 0, + }; + } +} \ No newline at end of file diff --git a/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs b/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..cfb5b54 --- /dev/null +++ b/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Client.Account.Menus; +using Client.Account.Services; +using RockPaperScissors.Common.Client; + +namespace Client.Account.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IAccountService CreateAccountService(this IClient client) + { + return new AccountService(client); + } + + public static IAccountMenu CreateAccountMenu(this IAccountService accountService) + { + return new AccountMenu(accountService); + } +} \ No newline at end of file diff --git a/Client/Client.Account/Menus/AccountMenu.cs b/Client/Client.Account/Menus/AccountMenu.cs new file mode 100644 index 0000000..5fa4b31 --- /dev/null +++ b/Client/Client.Account/Menus/AccountMenu.cs @@ -0,0 +1,154 @@ +using Client.Account.Enums; +using Client.Account.Extensions; +using Client.Account.Services; +using RockPaperScissors.Common.Enums; +using RockPaperScissors.Common.Extensions; + +namespace Client.Account.Menus; + +internal sealed class AccountMenu : IAccountMenu +{ + private readonly IAccountService _accountService; + + public AccountMenu(IAccountService accountService) + { + _accountService = accountService ?? throw new ArgumentNullException(nameof(accountService)); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + "Account Menu:".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.SignUp.GetValue()}.\t{MenuTypes.SignUp.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Login.GetValue()}.\t{MenuTypes.Login.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Back.GetValue()}.\t{MenuTypes.Back.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + + "\nPlease select an item from the list".Print(ConsoleColor.Green); + + Console.Write("Select -> "); + + var menuType = Console.ReadLine().TryGetMenuType(); + + if (menuType is MenuTypes.Unknown) + { + "Invalid input. Try again.".Print(ConsoleColor.Red); + + continue; + } + + switch (menuType) + { + case MenuTypes.SignUp: + var isRegistered = await RegisterAsync(cancellationToken); + + if (!isRegistered) + { + "\nPress any key to back to the start up menu list!".Print(ConsoleColor.Cyan); + Console.ReadKey(); + Console.Clear(); + + continue; + } + + "Trying logging in...".Print(ConsoleColor.White); + + return; + + case MenuTypes.Login: + var isSuccess = await LoginAsync(cancellationToken); + + if (isSuccess) + { + return; + } + + Console.Clear(); + + continue; + + case MenuTypes.Back: + return; + + default: + "Invalid input. Try again.".Print(ConsoleColor.Red); + continue; + } + } + } + + public async Task LogoutAsync(CancellationToken cancellationToken) + { + if (!_accountService.IsAuthorized()) + { + return true; + } + + var user = _accountService.GetUser(); + + var response = await _accountService.LogoutAsync(user.SessionId, cancellationToken); + + return response.Match( + _ => + { + "Successfully signed out".Print(ConsoleColor.DarkGreen); + + return true; + }, + exception => + { + exception.Message.Print(ConsoleColor.Red); + + return false; + }); + } + + private async Task RegisterAsync(CancellationToken cancellationToken) + { + ("We are glad to welcome you in the registration form!\n" + + "Please enter the required details\n" + + "to register an account on the platform") + .Print(ConsoleColor.Magenta); + + var login = "Login: ".BuildString(StringDestination.Login); + var password = "Password: ".BuildString(StringDestination.Password); + + var response = await _accountService.SignUpAsync(login, password, cancellationToken); + + return response.Match( + _ => + { + "Successfully registered!".Print(ConsoleColor.Green); + + return true; + }, + exception => + { + exception.Message.Print(ConsoleColor.Red); + + return false; + }); + } + + private async Task LoginAsync(CancellationToken cancellationToken) + { + var login = "Login: ".BuildString(StringDestination.Login); + var password = "Password: ".BuildString(StringDestination.Password, isNeedConfirmation: true); + + var response = await _accountService.LoginAsync(login, password, cancellationToken); + + return response.Match( + _ => + { + "Successfully signed in".Print(ConsoleColor.DarkGreen); + + return true; + }, + exception => + { + exception.Message.Print(ConsoleColor.Red); + + return false; + }); + } +} \ No newline at end of file diff --git a/Client/Client.Account/Menus/IAccountMenu.cs b/Client/Client.Account/Menus/IAccountMenu.cs new file mode 100644 index 0000000..49d6cab --- /dev/null +++ b/Client/Client.Account/Menus/IAccountMenu.cs @@ -0,0 +1,8 @@ +namespace Client.Account.Menus; + +public interface IAccountMenu +{ + Task StartAsync(CancellationToken cancellationToken); + + Task LogoutAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Client/Client.Account/Services/AccountService.cs b/Client/Client.Account/Services/AccountService.cs new file mode 100644 index 0000000..5ab9aeb --- /dev/null +++ b/Client/Client.Account/Services/AccountService.cs @@ -0,0 +1,104 @@ +using OneOf; +using RockPaperScissors.Common; +using RockPaperScissors.Common.Client; +using RockPaperScissors.Common.Extensions; +using RockPaperScissors.Common.Requests; +using RockPaperScissors.Common.Responses; + +namespace Client.Account.Services; + +internal sealed class AccountService : IAccountService +{ + private readonly IClient _client; + private IUser _user = null!; + + public AccountService(IClient client) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public Task> SignUpAsync( + string login, string password, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(login)) + { + return Task.FromResult(OneOf.FromT1(new CustomException("Login must not be 'null' or '\"\"'"))); + } + + if (string.IsNullOrEmpty(password)) + { + return Task.FromResult(OneOf.FromT1(new CustomException("Password must not be 'null' or '\"\"'"))); + } + + var request = new RegisterRequest + { + Login = login, + Password = password + }; + + var response = + _client.PostAsync("api/Account/register", request, cancellationToken); + + return response; + } + + public async Task> LoginAsync( + string login, string password, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(login)) + { + return new CustomException("Login must not be 'null' or '\"\"'"); + } + + if (string.IsNullOrEmpty(password)) + { + return new CustomException("Password must not be 'null' or '\"\"'"); + } + + var request = new LoginRequest + { + Login = login, + Password = password + }; + + var response = + await _client.PostAsync("api/Account/login", request, cancellationToken); + + if (!response.IsT0) + { + return response; + } + + var loginResponse = response.AsT0; + _user = new User(loginResponse.Token, loginResponse.Login); + + return response; + } + + public Task> LogoutAsync( + string? token, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(token)) + { + return Task.FromResult(OneOf.FromT1(new CustomException("Token must not be 'null' or '\"\"'"))); + } + + var queryUrl = new QueryBuilder(1) + { + { "sessionId", token } + }; + var url = $"api/Account/logout{queryUrl}"; + + var response = + _client.GetAsync(url, ("Authorization", token), cancellationToken); + + return response; + } + + public IUser GetUser() => _user; + + public bool IsAuthorized() + { + return _user is not null && _user.IsAuthorized; + } +} \ No newline at end of file diff --git a/Client/Client.Account/Services/IAccountService.cs b/Client/Client.Account/Services/IAccountService.cs new file mode 100644 index 0000000..42ebac5 --- /dev/null +++ b/Client/Client.Account/Services/IAccountService.cs @@ -0,0 +1,25 @@ +using OneOf; +using RockPaperScissors.Common; +using RockPaperScissors.Common.Responses; + +namespace Client.Account.Services; + +public interface IAccountService +{ + Task> SignUpAsync(string login, + string password, + CancellationToken cancellationToken = default); + + Task> LoginAsync( + string login, + string password, + CancellationToken cancellationToken = default); + + Task> LogoutAsync( + string? token, + CancellationToken cancellationToken = default); + + IUser GetUser(); + + bool IsAuthorized(); +} \ No newline at end of file diff --git a/Client/Client.Account/Services/IUser.cs b/Client/Client.Account/Services/IUser.cs new file mode 100644 index 0000000..ca16f20 --- /dev/null +++ b/Client/Client.Account/Services/IUser.cs @@ -0,0 +1,12 @@ +namespace Client.Account.Services; + +public interface IUser +{ + public string? SessionId { get; } + + public string? Login { get; } + + bool IsAuthorized => !string.IsNullOrEmpty(SessionId) && !string.IsNullOrEmpty(Login); + + string GetBearerToken(); +} \ No newline at end of file diff --git a/Client/Client.Account/Services/User.cs b/Client/Client.Account/Services/User.cs new file mode 100644 index 0000000..0d47652 --- /dev/null +++ b/Client/Client.Account/Services/User.cs @@ -0,0 +1,25 @@ +namespace Client.Account.Services; + +internal sealed class User : IUser +{ + public static readonly User Default = new(string.Empty, string.Empty); + + internal User(string sessionId, string login) + { + SessionId = sessionId; + Login = login; + } + + public string SessionId { get; } + + public string Login { get; } + + + public bool IsAuthorized => !string.IsNullOrEmpty(SessionId) && !string.IsNullOrEmpty(Login); + + + public string GetBearerToken() + { + return $"Bearer {SessionId}"; + } +} \ No newline at end of file diff --git a/Client/Client.Host/Client.Host.csproj b/Client/Client.Host/Client.Host.csproj new file mode 100644 index 0000000..5bfac12 --- /dev/null +++ b/Client/Client.Host/Client.Host.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6 + enable + + + + + + + + + + + + + diff --git a/Client/ClientAppEmulator.cs b/Client/Client.Host/ClientAppEmulator.cs similarity index 99% rename from Client/ClientAppEmulator.cs rename to Client/Client.Host/ClientAppEmulator.cs index bd40c9c..6adbb76 100644 --- a/Client/ClientAppEmulator.cs +++ b/Client/Client.Host/ClientAppEmulator.cs @@ -4,15 +4,15 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using Client.Models; -using Client.Models.Interfaces; -using Client.Services; -using Client.Services.RequestProcessor; -using Client.Services.RequestProcessor.RequestModels.Impl; +using Client.Host.Models; +using Client.Host.Models.Interfaces; +using Client.Host.Services; +using Client.Host.Services.RequestProcessor; +using Client.Host.Services.RequestProcessor.RequestModels.Impl; using Newtonsoft.Json; using NLog; -namespace Client +namespace Client.Host { public class ClientAppEmulator { diff --git a/Client/Client.Host/Extensions/JsonExtension.cs b/Client/Client.Host/Extensions/JsonExtension.cs new file mode 100644 index 0000000..f229a49 --- /dev/null +++ b/Client/Client.Host/Extensions/JsonExtension.cs @@ -0,0 +1,38 @@ +// using Client.Host.Services.RequestProcessor.RequestModels; +// using Newtonsoft.Json; +// using Newtonsoft.Json.Linq; +// using Newtonsoft.Json.Schema; +// +// namespace Client.Host.Extensions; +// +// public static class JsonExtension +// { +// private const string Schema = @"{ +// 'code': {'type': 'integer'}, +// 'message': {'type': 'string'}, +// }"; +// +// private static JSchema JSchema => JSchema.Parse(Schema); +// +// public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() +// { +// if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) +// { +// deserialized = default; +// return false; +// } +// +// var jObject = JObject.Parse(json.Content); +// +// var isValid = jObject.IsValid(JSchema); +// +// if (!isValid) +// { +// deserialized = default; +// return false; +// } +// +// deserialized = JsonConvert.DeserializeObject(json.Content); +// return true; +// } +// } \ No newline at end of file diff --git a/Client/Client.Host/Menus/AccountMenu.cs b/Client/Client.Host/Menus/AccountMenu.cs new file mode 100644 index 0000000..77ca133 --- /dev/null +++ b/Client/Client.Host/Menus/AccountMenu.cs @@ -0,0 +1,101 @@ +// using System; +// using System.Collections.Generic; +// using System.Threading.Tasks; +// using Client.Host.Extensions; +// using Client.Host.Models; +// using Client.Host.Services; +// using Client.Host.Services.RequestProcessor; +// using Client.Host.Services.RequestProcessor.RequestModels; +// using Client.Host.Services.RequestProcessor.RequestModels.Impl; +// using Newtonsoft.Json; +// +// namespace Client.Host.Menus; +// +// public class AccountMenu : IAccountMenu +// { +// private readonly IRequestPerformer _performer; +// +// public AccountMenu(IRequestPerformer performer) +// { +// _performer = performer; +// } +// +// public async Task RegisterAsync() +// { +// TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + +// "Please enter the required details\n" + +// "to register an account on the platform", ConsoleColor.Magenta); +// var registrationAccount = new Account +// { +// Login = new StringPlaceholder().BuildString("Login"), +// Password = +// new StringPlaceholder(StringDestination.Password).BuildString("Password") +// }; +// +// var options = new RequestOptions +// { +// ContentType = "application/json", +// Body = JsonConvert.SerializeObject(registrationAccount), +// Address = "account/register", +// IsValid = true, +// Method = RequestMethod.Post, +// Name = "Registration" +// }; +// var reachedResponse = await _performer.PerformRequestAsync(options); +// +// if (reachedResponse.TryParseJson(out var errorModel)) +// { +// TextWrite.Print(errorModel.Message, ConsoleColor.Red); +// return false; +// } +// +// TextWrite.Print("Successfully registered!", ConsoleColor.Green); +// return true; +// } +// +// public async Task<(string token, TokenModel inputAccount)> LoginAsync() +// { +// var inputAccount = new Account +// { +// Login = new StringPlaceholder().BuildString("Login"), +// Password = +// new StringPlaceholder(StringDestination.Password).BuildString("Password", true) +// }; +// var options = new RequestOptions +// { +// ContentType = "application/json", +// Body = JsonConvert.SerializeObject(inputAccount), +// Address = "account/login", +// IsValid = true, +// Method = RequestMethod.Post, +// Name = "Login" +// }; +// var reachedResponse = await _performer.PerformRequestAsync(options); +// +// if (reachedResponse.TryParseJson(out var tokenModel)) +// { +// TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); +// return (tokenModel.Token, tokenModel); +// } +// +// var error = JsonConvert.DeserializeObject(reachedResponse.Content); +// if(error is null) return (null, null); +// +// TextWrite.Print(error.Message, ConsoleColor.Red); +// return (null, null); +// } +// +// public async Task LogoutAsync(string token) +// { +// var options = new RequestOptions +// { +// Headers = new Dictionary{{"Authorization",token}}, +// Address = $"user/logout/{token}", +// IsValid = true, +// Method = RequestMethod.Get +// }; +// await _performer.PerformRequestAsync(options); +// TextWrite.Print("Successfully signed out", ConsoleColor.Green); +// return true; +// } +// } \ No newline at end of file diff --git a/Client/Client.Host/Menus/IAccountMenu.cs b/Client/Client.Host/Menus/IAccountMenu.cs new file mode 100644 index 0000000..2c199f5 --- /dev/null +++ b/Client/Client.Host/Menus/IAccountMenu.cs @@ -0,0 +1,13 @@ +// using System.Threading.Tasks; +// using Client.Host.Models; +// +// namespace Client.Host.Menus; +// +// public interface IAccountMenu +// { +// Task RegisterAsync(); +// +// Task<(string token, TokenModel inputAccount)> LoginAsync(); +// +// Task LogoutAsync(string token); +// } \ No newline at end of file diff --git a/Client/Menus/IMainMenu.cs b/Client/Client.Host/Menus/IMainMenu.cs similarity index 75% rename from Client/Menus/IMainMenu.cs rename to Client/Client.Host/Menus/IMainMenu.cs index 39e2eec..40e613a 100644 --- a/Client/Menus/IMainMenu.cs +++ b/Client/Client.Host/Menus/IMainMenu.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Client.Menus; +namespace Client.Host.Menus; public interface IMainMenu { diff --git a/Client/Menus/MainMenu.cs b/Client/Client.Host/Menus/MainMenu.cs similarity index 94% rename from Client/Menus/MainMenu.cs rename to Client/Client.Host/Menus/MainMenu.cs index 5c8b80d..99aae7f 100644 --- a/Client/Menus/MainMenu.cs +++ b/Client/Client.Host/Menus/MainMenu.cs @@ -1,10 +1,10 @@ -using System; +/*using System; using System.Threading.Tasks; -using Client.Models; -using Client.Services; -using Client.Services.RequestProcessor; +using Client.Host.Models; +using Client.Host.Services; +using Client.Host.Services.RequestProcessor; -namespace Client.Menus; +namespace Client.Host.Menus; public class MainMenu : IMainMenu { @@ -84,4 +84,4 @@ public async Task PlayerMenu() } } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Client/Client.Host/Menus/StartMenu.cs b/Client/Client.Host/Menus/StartMenu.cs new file mode 100644 index 0000000..474e6a8 --- /dev/null +++ b/Client/Client.Host/Menus/StartMenu.cs @@ -0,0 +1,121 @@ +// using System; +// using System.Threading; +// using System.Threading.Tasks; +// using Client.Host.Models; +// using Client.Host.Services; +// using Client.Host.Services.RequestProcessor; +// +// namespace Client.Host.Menus; +// +// public class StartMenu +// { +// private readonly IAccountMenu _accountMenu; +// private readonly IStatisticsService _statisticsService; +// private readonly IRequestPerformer _requestPerformer; +// +// private string SessionId { get; set; } +// +// public StartMenu(IRequestPerformer performer) +// { +// _requestPerformer = performer; +// _statisticsService = new StatisticsService(performer); +// _accountMenu = new AccountMenu(performer); +// } +// +// public async Task StartAsync() +// { +// var tokenSource = new CancellationTokenSource(); +// var token = tokenSource.Token; +// +// await Greeting().ConfigureAwait(false); +// TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); +// +// Console.ReadKey(); +// Console.Clear(); +// //todo: trying to connect to the server +// await Menu(token); +// return 1; +// } +// private async Task Menu(CancellationToken token) +// { +// while (true) +// { +// TextWrite.Print("Start menu:\n" + +// "1.\tSign up\n" + +// "2.\tLog in\n" + +// "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics +// "4.\tExit", ConsoleColor.DarkYellow); +// +// TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); +// +// Console.Write("Select -> "); +// if (token.IsCancellationRequested) +// { +// return; +// } +// var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); +// if (!passed) +// { +// TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); +// continue; +// } +// switch (startMenuInput) +// { +// case 1: +// await _accountMenu.RegisterAsync(); +// TextWrite.Print( +// "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); +// Console.ReadKey(); +// Console.Clear(); +// break; +// case 2: +// TokenModel inputAccount; +// (SessionId, inputAccount) = await _accountMenu.LoginAsync(); +// if (!string.IsNullOrEmpty(SessionId)) +// { +// Console.Clear(); +// await new MainMenu(inputAccount,_requestPerformer,_statisticsService) +// .PlayerMenu(); +// } +// Console.Clear(); +// break; +// case 3: +// var result = await _statisticsService.GetAllStatistics(); +// await _statisticsService.PrintStatistics(result); +// /*var statistics = await OverallStatistics(); +// if(statistics == null) +// Console.WriteLine("No statistics so far"); +// else +// { +// PrintStatistics(statistics); +// }*/ +// break; +// case 4: +// if (await _accountMenu.LogoutAsync(SessionId)) +// { +// Console.WriteLine("DEBUG: Logged out"); +// return; +// } +// else +// { +// throw new NotImplementedException(); +// } +// default: +// TextWrite.Print("Unsupported input", ConsoleColor.Red); +// continue; +// } +// } +// } +// +// private static Task Greeting() +// { +// TextWrite.Print( +// "VERSION 2.0\n" + +// "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + +// "You are given the opportunity to compete with other users in this wonderful game,\n" + +// "or if you don’t have anyone to play, don’t worry,\n" + +// "you can find a random player or just try your skill with a bot.", ConsoleColor.White); +// TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); +// return Task.CompletedTask; +// } +// } \ No newline at end of file diff --git a/Client/Client.Host/Models/Account.cs b/Client/Client.Host/Models/Account.cs new file mode 100644 index 0000000..cf6d805 --- /dev/null +++ b/Client/Client.Host/Models/Account.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using Client.Host.Models.Interfaces; + +namespace Client.Host.Models; + +public class Account : IAccount +{ + [JsonPropertyName("Login")] + public string Login { get; set; } + + [JsonPropertyName("Password")] + public string Password { get; set; } +} \ No newline at end of file diff --git a/Client/Client.Host/Models/ErrorModel.cs b/Client/Client.Host/Models/ErrorModel.cs new file mode 100644 index 0000000..86fa019 --- /dev/null +++ b/Client/Client.Host/Models/ErrorModel.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace Client.Host.Models; + +public class ErrorModel +{ + [JsonPropertyName("Code")] + public int Code { get; set; } + [JsonPropertyName("Message")] + public string Message { get; set; } +} \ No newline at end of file diff --git a/Client/Models/Interfaces/IAccount.cs b/Client/Client.Host/Models/Interfaces/IAccount.cs similarity index 71% rename from Client/Models/Interfaces/IAccount.cs rename to Client/Client.Host/Models/Interfaces/IAccount.cs index 758adc8..31fd162 100644 --- a/Client/Models/Interfaces/IAccount.cs +++ b/Client/Client.Host/Models/Interfaces/IAccount.cs @@ -1,4 +1,4 @@ -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; public interface IAccount { diff --git a/Client/Models/Interfaces/IOverallStatistics.cs b/Client/Client.Host/Models/Interfaces/IOverallStatistics.cs similarity index 70% rename from Client/Models/Interfaces/IOverallStatistics.cs rename to Client/Client.Host/Models/Interfaces/IOverallStatistics.cs index 68484a4..749a66f 100644 --- a/Client/Models/Interfaces/IOverallStatistics.cs +++ b/Client/Client.Host/Models/Interfaces/IOverallStatistics.cs @@ -1,4 +1,4 @@ -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; public interface IOverallStatistics { diff --git a/Client/Models/Interfaces/IRoom.cs b/Client/Client.Host/Models/Interfaces/IRoom.cs similarity index 58% rename from Client/Models/Interfaces/IRoom.cs rename to Client/Client.Host/Models/Interfaces/IRoom.cs index 5065dfe..e9af15e 100644 --- a/Client/Models/Interfaces/IRoom.cs +++ b/Client/Client.Host/Models/Interfaces/IRoom.cs @@ -1,21 +1,21 @@ using System; using System.Collections.Concurrent; -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; internal interface IRoom { - [JsonProperty("RoomId")] + [JsonPropertyName("RoomId")] string RoomId { get; set; } - [JsonProperty("Players")] + [JsonPropertyName("Players")] ConcurrentDictionary Players { get; set; } - [JsonProperty("CurrentRoundId")] + [JsonPropertyName("CurrentRoundId")] string CurrentRoundId { get; set; } - [JsonProperty("CreationTime")] + [JsonPropertyName("CreationTime")] DateTime CreationTime { get; set; } bool IsReady { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IRound.cs b/Client/Client.Host/Models/Interfaces/IRound.cs similarity index 61% rename from Client/Models/Interfaces/IRound.cs rename to Client/Client.Host/Models/Interfaces/IRound.cs index 79561a0..cc013ba 100644 --- a/Client/Models/Interfaces/IRound.cs +++ b/Client/Client.Host/Models/Interfaces/IRound.cs @@ -1,26 +1,26 @@ using System; using System.Collections.Concurrent; -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; internal interface IRound { - [JsonProperty("Id")] + [JsonPropertyName("Id")] string Id { get; init; } //Not to store identical rounds - [JsonProperty("Moves")] + [JsonPropertyName("Moves")] ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - [JsonProperty("IsFinished")] + [JsonPropertyName("IsFinished")] bool IsFinished { get; set; } //Probably not needed. - [JsonProperty("TimeFinished")] + [JsonPropertyName("TimeFinished")] DateTime TimeFinished { get; set; } - [JsonProperty("WinnerId")] + [JsonPropertyName("WinnerId")] string WinnerId { get; set; } - [JsonProperty("LoserId")] + [JsonPropertyName("LoserId")] string LoserId { get; set; } } \ No newline at end of file diff --git a/Client/Models/Interfaces/IStatistics.cs b/Client/Client.Host/Models/Interfaces/IStatistics.cs similarity index 96% rename from Client/Models/Interfaces/IStatistics.cs rename to Client/Client.Host/Models/Interfaces/IStatistics.cs index c9bed6d..8886343 100644 --- a/Client/Models/Interfaces/IStatistics.cs +++ b/Client/Client.Host/Models/Interfaces/IStatistics.cs @@ -1,4 +1,4 @@ -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; public interface IStatistics { diff --git a/Client/Models/Interfaces/StatisticsDto.cs b/Client/Client.Host/Models/Interfaces/StatisticsDto.cs similarity index 59% rename from Client/Models/Interfaces/StatisticsDto.cs rename to Client/Client.Host/Models/Interfaces/StatisticsDto.cs index 2aa1e20..10418cd 100644 --- a/Client/Models/Interfaces/StatisticsDto.cs +++ b/Client/Client.Host/Models/Interfaces/StatisticsDto.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace Client.Models.Interfaces; +namespace Client.Host.Models.Interfaces; public class StatisticsDto { - [JsonProperty("Login")] + [JsonPropertyName("Login")] public string Login { get; set; } - [JsonProperty("Score")] + [JsonPropertyName("Score")] public int Score { get; set; } public override string ToString() diff --git a/Client/Models/Room.cs b/Client/Client.Host/Models/Room.cs similarity index 56% rename from Client/Models/Room.cs rename to Client/Client.Host/Models/Room.cs index 53b8eb0..c06f172 100644 --- a/Client/Models/Room.cs +++ b/Client/Client.Host/Models/Room.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Concurrent; -using Client.Models.Interfaces; +using System.Text.Json.Serialization; +using Client.Host.Models.Interfaces; -namespace Client.Models; +namespace Client.Host.Models; public class Room : IRoom { - [JsonProperty("RoomId")] + [JsonPropertyName("RoomId")] public string RoomId { get; set; } - [JsonProperty("Players")] + [JsonPropertyName("Players")] public ConcurrentDictionary Players { get; set; } - [JsonProperty("CurrentRoundId")] + [JsonPropertyName("CurrentRoundId")] public string CurrentRoundId { get; set; } - [JsonProperty("CreationTime")] + [JsonPropertyName("CreationTime")] public DateTime CreationTime { get; set; } public bool IsReady { get; set; } diff --git a/Client/Models/Round.cs b/Client/Client.Host/Models/Round.cs similarity index 59% rename from Client/Models/Round.cs rename to Client/Client.Host/Models/Round.cs index 1449c9b..dfa12c5 100644 --- a/Client/Models/Round.cs +++ b/Client/Client.Host/Models/Round.cs @@ -1,30 +1,30 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Concurrent; -using Client.Models.Interfaces; +using System.Text.Json.Serialization; +using Client.Host.Models.Interfaces; -namespace Client.Models; +namespace Client.Host.Models; internal class Round : IRound { - [JsonProperty("Id")] + [JsonPropertyName("Id")] public string Id { get; init; } //Not to store identical rounds - [JsonProperty("Moves")] + [JsonPropertyName("Moves")] public ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - [JsonProperty("IsFinished")] + [JsonPropertyName("IsFinished")] public bool IsFinished { get; set; } //Probably not needed. - [JsonProperty("TimeFinished")] + [JsonPropertyName("TimeFinished")] public DateTime TimeFinished { get; set; } - [JsonProperty("WinnerId")] + [JsonPropertyName("WinnerId")] public string WinnerId { get; set; } - [JsonProperty("LoserId")] + [JsonPropertyName("LoserId")] public string LoserId { get; set; } - [JsonProperty("IsDraw")] + [JsonPropertyName("IsDraw")] public bool IsDraw { get; set; } } \ No newline at end of file diff --git a/Client/Models/Statistics.cs b/Client/Client.Host/Models/Statistics.cs similarity index 96% rename from Client/Models/Statistics.cs rename to Client/Client.Host/Models/Statistics.cs index ce99282..5eb1cbf 100644 --- a/Client/Models/Statistics.cs +++ b/Client/Client.Host/Models/Statistics.cs @@ -1,6 +1,6 @@ -using Client.Models.Interfaces; +using Client.Host.Models.Interfaces; -namespace Client.Models; +namespace Client.Host.Models; public class Statistics : IStatistics, IOverallStatistics { diff --git a/Client/Models/TokenModel.cs b/Client/Client.Host/Models/TokenModel.cs similarity index 55% rename from Client/Models/TokenModel.cs rename to Client/Client.Host/Models/TokenModel.cs index 850e0cd..51e83f9 100644 --- a/Client/Models/TokenModel.cs +++ b/Client/Client.Host/Models/TokenModel.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace Client.Models; +namespace Client.Host.Models; public class TokenModel { [JsonIgnore] public string BearerToken => "Bearer " + Token; - [JsonProperty("Token")] + [JsonPropertyName("Token")] public string Token {get; set; } - [JsonProperty("Login")] + [JsonPropertyName("Login")] public string Login { get; set; } } \ No newline at end of file diff --git a/Client/Client.Host/Program.cs b/Client/Client.Host/Program.cs new file mode 100644 index 0000000..083e112 --- /dev/null +++ b/Client/Client.Host/Program.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Client.Account.Extensions; +using Client.StartMenu.Extensions; +using Client.Statistics.Extensions; + +namespace Client.Host; + +internal static class Program +{ + // private static async Task MainV1() + // { + // try + // { + // var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; + // var clientHandler = new HttpClientHandler(); + // var requestHandler = new RequestHandler(client, clientHandler); + // var requestPerformer = new RequestPerformer(requestHandler); + // + // var startMenu = new StartMenu(requestPerformer); + // return await startMenu.StartAsync(); + // } + // catch (Exception) //todo : do this need a message? + // { + // Console.WriteLine("Unknown error occured. Crash."); + // return -1; + // } + // } + + private static async Task Main() + { + var cancellationToken = new CancellationTokenSource(); + try + { + var client = RockPaperScissors.Common.Client.Client.Create("http://localhost:5000"); + var accountService = client.CreateAccountService(); + var statisticsService = client.CreateStatisticsService(); + + var accountMenu = accountService.CreateAccountMenu(); + var statisticsMenu = statisticsService.CreateStatisticsMenu(accountService); + var startMenu = statisticsMenu.CreateStartMenu(accountMenu); + + await startMenu.PrintAsync(cancellationToken.Token); + + return 0; + } + catch (Exception exception) //todo : do we need a message? + { + Console.WriteLine(exception.Message); + Console.WriteLine("Unknown error occured. Closing."); + + return -1; + } + } +} \ No newline at end of file diff --git a/Client/Services/RequestProcessor/IRequestHandler.cs b/Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs similarity index 56% rename from Client/Services/RequestProcessor/IRequestHandler.cs rename to Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs index 17295b8..4ea855c 100644 --- a/Client/Services/RequestProcessor/IRequestHandler.cs +++ b/Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using Client.Services.RequestProcessor.RequestModels; +using Client.Host.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor; +namespace Client.Host.Services.RequestProcessor; public interface IRequestHandler { diff --git a/Client/Services/RequestProcessor/IRequestPerformer.cs b/Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs similarity index 57% rename from Client/Services/RequestProcessor/IRequestPerformer.cs rename to Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs index 4272be9..0793cf0 100644 --- a/Client/Services/RequestProcessor/IRequestPerformer.cs +++ b/Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using Client.Services.RequestProcessor.RequestModels; +using Client.Host.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor; +namespace Client.Host.Services.RequestProcessor; public interface IRequestPerformer { diff --git a/Client/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs similarity index 93% rename from Client/Services/RequestProcessor/Impl/RequestHandler.cs rename to Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs index 64d4fd8..0db90d9 100644 --- a/Client/Services/RequestProcessor/Impl/RequestHandler.cs +++ b/Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs @@ -1,11 +1,11 @@ -using Client.Services.RequestProcessor.RequestModels.Impl; -using System; +using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Client.Services.RequestProcessor.RequestModels; +using Client.Host.Services.RequestProcessor.RequestModels; +using Client.Host.Services.RequestProcessor.RequestModels.Impl; -namespace Client.Services.RequestProcessor.Impl; +namespace Client.Host.Services.RequestProcessor.Impl; public class RequestHandler : IRequestHandler { @@ -17,6 +17,7 @@ public RequestHandler(HttpClient httpClient, HttpClientHandler httpClientHandler _client = httpClient; _httpClientHandler = httpClientHandler; } + public async Task HandleRequestAsync(IRequestOptions requestOptions) { //var handler = new HttpClientHandler(); diff --git a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs b/Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs similarity index 87% rename from Client/Services/RequestProcessor/Impl/RequestPerformer.cs rename to Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs index 4f1b401..ff006d1 100644 --- a/Client/Services/RequestProcessor/Impl/RequestPerformer.cs +++ b/Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using Client.Services.RequestProcessor.RequestModels; +using Client.Host.Services.RequestProcessor.RequestModels; -namespace Client.Services.RequestProcessor.Impl; +namespace Client.Host.Services.RequestProcessor.Impl; public class RequestPerformer : IRequestPerformer { diff --git a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs similarity index 76% rename from Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs rename to Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs index de6583e..d8ac9b7 100644 --- a/Client/Services/RequestProcessor/RequestModels/IRequestOptions.cs +++ b/Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs @@ -1,14 +1,20 @@ using System.Collections.Generic; -namespace Client.Services.RequestProcessor.RequestModels; +namespace Client.Host.Services.RequestProcessor.RequestModels; public interface IRequestOptions { Dictionary Headers { get; } + string Name { get; } + string Address { get; } + RequestMethod Method { get; } + string ContentType { get; } + string Body { get; } + bool IsValid { get; } } \ No newline at end of file diff --git a/Client/Services/RequestProcessor/RequestModels/IResponse.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs similarity index 65% rename from Client/Services/RequestProcessor/RequestModels/IResponse.cs rename to Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs index 13f7506..0a6d2ab 100644 --- a/Client/Services/RequestProcessor/RequestModels/IResponse.cs +++ b/Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs @@ -1,4 +1,4 @@ -namespace Client.Services.RequestProcessor.RequestModels; +namespace Client.Host.Services.RequestProcessor.RequestModels; public interface IResponse { diff --git a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs similarity index 87% rename from Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs rename to Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs index 8d6f076..2e5c2ff 100644 --- a/Client/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs +++ b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Client.Services.RequestProcessor.RequestModels.Impl; +namespace Client.Host.Services.RequestProcessor.RequestModels.Impl; public class RequestOptions : IRequestOptions { diff --git a/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs similarity index 80% rename from Client/Services/RequestProcessor/RequestModels/Impl/Response.cs rename to Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs index fa67569..6ce3d44 100644 --- a/Client/Services/RequestProcessor/RequestModels/Impl/Response.cs +++ b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs @@ -1,4 +1,4 @@ -namespace Client.Services.RequestProcessor.RequestModels.Impl; +namespace Client.Host.Services.RequestProcessor.RequestModels.Impl; public class Response : IResponse { diff --git a/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs similarity index 64% rename from Client/Services/RequestProcessor/RequestModels/RequestMethod.cs rename to Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs index 88a7555..fae404e 100644 --- a/Client/Services/RequestProcessor/RequestModels/RequestMethod.cs +++ b/Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs @@ -1,4 +1,4 @@ -namespace Client.Services.RequestProcessor.RequestModels; +namespace Client.Host.Services.RequestProcessor.RequestModels; public enum RequestMethod { diff --git a/Client/Client.Host/Services/RoomService.cs b/Client/Client.Host/Services/RoomService.cs new file mode 100644 index 0000000..86e5e6c --- /dev/null +++ b/Client/Client.Host/Services/RoomService.cs @@ -0,0 +1,59 @@ +// using Client.Host.Extensions; +// using Client.Host.Models; +// using Client.Host.Services.RequestProcessor; +// using Client.Host.Services.RequestProcessor.RequestModels; +// using Client.Host.Services.RequestProcessor.RequestModels.Impl; +// using Newtonsoft.Json; +// +// namespace Client.Host.Services; +// +// public class RoomService : IRoomService +// { +// private readonly TokenModel _tokenModel; +// private readonly IRequestPerformer _requestPerformer; +// +// public RoomService(TokenModel tokenModel, +// IRequestPerformer requestPerformer) +// { +// _tokenModel = tokenModel; +// _requestPerformer = requestPerformer; +// } +// public async Task CreateRoom(bool isPrivate, bool isTraining) +// { +// Console.WriteLine("Trying to create a room."); +// +// var options = new RequestOptions +// { +// Address = $"room/create?isPrivate={isPrivate}", +// IsValid = true, +// Headers = new Dictionary +// { +// { +// "Authorization", _tokenModel.BearerToken +// }, +// { +// "X-Training", isTraining.ToString() +// } +// }, +// Method = RequestMethod.Post, +// Body = string.Empty, +// Name = "Create Room" +// }; +// var reachedResponse = await _requestPerformer +// .PerformRequestAsync(options); +// +// if (reachedResponse.TryParseJson(out var errorModel)) +// { +// TextWrite.Print(errorModel.Message, ConsoleColor.Red); +// return null; +// } +// +// var room = JsonConvert.DeserializeObject(reachedResponse.Content); +// return room; +// } +// } +// +// public interface IRoomService +// { +// Task CreateRoom(bool isPrivate, bool isTraining); +// } \ No newline at end of file diff --git a/Client/Client.Host/Services/StatisticsService.cs b/Client/Client.Host/Services/StatisticsService.cs new file mode 100644 index 0000000..f8ce6e0 --- /dev/null +++ b/Client/Client.Host/Services/StatisticsService.cs @@ -0,0 +1,74 @@ +// using System; +// using System.Collections.Generic; +// using System.Threading.Tasks; +// using Client.Host.Models; +// using Client.Host.Models.Interfaces; +// using Client.Host.Services.RequestProcessor; +// using Client.Host.Services.RequestProcessor.RequestModels; +// using Client.Host.Services.RequestProcessor.RequestModels.Impl; +// using Mapster; +// using Newtonsoft.Json; +// +// namespace Client.Host.Services; +// +// public class StatisticsService: IStatisticsService +// { +// private readonly IRequestPerformer _requestPerformer; +// +// public StatisticsService(IRequestPerformer requestPerformer) +// { +// _requestPerformer = requestPerformer; +// } +// +// public async Task GetAllStatistics() +// { +// var options = new RequestOptions +// { +// ContentType = "none", +// Address = "stats/all", +// IsValid = true, +// Method = RequestMethod.Get, +// Name = "OverallStats" +// }; +// +// var response = await _requestPerformer.PerformRequestAsync(options); +// +// var toConvert = JsonConvert.DeserializeObject(response.Content); +// return toConvert?.Adapt(); +// } +// +// public async Task GetPersonalStatistics(string token) +// { +// var options = new RequestOptions +// { +// Headers = new Dictionary{{"Authorization",token}}, +// ContentType = "none", +// Address = "stats/personal", +// IsValid = true, +// Method = RequestMethod.Get, +// Name = "PersonalStats" +// }; +// +// var response = await _requestPerformer.PerformRequestAsync(options); +// +// return response.Content != null ? JsonConvert.DeserializeObject(response.Content) : null; +// } +// +// public Task PrintStatistics(IOverallStatistics[] statistics) +// { +// for(var i = 0; i < statistics.Length; i++) +// { +// TextWrite.Print($"{i+1}. User: {statistics[i].Account.Login}\n" + +// $"Score: {statistics[i].Score}", ConsoleColor.White); +// } +// Console.Write('\n'); +// return Task.CompletedTask; +// } +// } +// +// public interface IStatisticsService +// { +// Task GetAllStatistics(); +// Task GetPersonalStatistics(string token); +// Task PrintStatistics(IOverallStatistics[] statistics); +// } \ No newline at end of file diff --git a/Client/Client.StartMenu/Client.StartMenu.csproj b/Client/Client.StartMenu/Client.StartMenu.csproj new file mode 100644 index 0000000..4d8587f --- /dev/null +++ b/Client/Client.StartMenu/Client.StartMenu.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/Client/Client.StartMenu/Enums/MenuTypes.cs b/Client/Client.StartMenu/Enums/MenuTypes.cs new file mode 100644 index 0000000..c9524ea --- /dev/null +++ b/Client/Client.StartMenu/Enums/MenuTypes.cs @@ -0,0 +1,9 @@ +namespace Client.StartMenu.Enums; + +internal enum MenuTypes +{ + Unknown = 0, + Account = 1, + Leaderboard = 2, + Exit = 3, +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Extensions/EnumExtensions.cs b/Client/Client.StartMenu/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..3f1898a --- /dev/null +++ b/Client/Client.StartMenu/Extensions/EnumExtensions.cs @@ -0,0 +1,39 @@ +using Client.StartMenu.Enums; + +namespace Client.StartMenu.Extensions; + +internal static class EnumExtensions +{ + internal static string GetDisplayName(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Account => "Account", + MenuTypes.Leaderboard => "Leaderboard", + MenuTypes.Exit => "Exit", + _ => "Unknown", + }; + } + + internal static MenuTypes TryGetMenuType(this string? stringInput) + { + return stringInput switch + { + "3" => MenuTypes.Exit, + "2" => MenuTypes.Leaderboard, + "1" => MenuTypes.Account, + _ => MenuTypes.Unknown, + }; + } + + internal static int GetValue(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Exit => 3, + MenuTypes.Leaderboard => 2, + MenuTypes.Account => 1, + _ => 0, + }; + } +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs b/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..3e7e90c --- /dev/null +++ b/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using Client.Account.Menus; +using Client.StartMenu.Menus; +using Client.Statistics.Menus; + +namespace Client.StartMenu.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IStartMenu CreateStartMenu(this IStatisticsMenu statisticsMenu, IAccountMenu accountMenu) + { + ArgumentNullException.ThrowIfNull(accountMenu); + ArgumentNullException.ThrowIfNull(statisticsMenu); + + return new Menus.StartMenu(accountMenu, statisticsMenu); + } +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Menus/IStartMenu.cs b/Client/Client.StartMenu/Menus/IStartMenu.cs new file mode 100644 index 0000000..81967ba --- /dev/null +++ b/Client/Client.StartMenu/Menus/IStartMenu.cs @@ -0,0 +1,6 @@ +namespace Client.StartMenu.Menus; + +public interface IStartMenu +{ + Task PrintAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Menus/StartMenu.cs b/Client/Client.StartMenu/Menus/StartMenu.cs new file mode 100644 index 0000000..c305752 --- /dev/null +++ b/Client/Client.StartMenu/Menus/StartMenu.cs @@ -0,0 +1,90 @@ +using Client.Account.Menus; +using Client.StartMenu.Enums; +using Client.StartMenu.Extensions; +using Client.Statistics.Menus; +using RockPaperScissors.Common.Extensions; + +namespace Client.StartMenu.Menus; + +internal sealed class StartMenu: IStartMenu +{ + private readonly IAccountMenu _accountMenu; + private readonly IStatisticsMenu _statisticsMenu; + + public StartMenu(IAccountMenu accountMenu, IStatisticsMenu statisticsMenu) + { + _accountMenu = accountMenu ?? throw new ArgumentNullException(nameof(accountMenu)); + _statisticsMenu = statisticsMenu ?? throw new ArgumentNullException(nameof(statisticsMenu)); + } + + public Task PrintAsync(CancellationToken cancellationToken) + { + PrintGreeting(); + + "\nPress any key to show start up menu list.".Print(ConsoleColor.Green); + + Console.ReadKey(); + Console.Clear(); + //todo: trying to connect to the server + + return ShowStartAsync(cancellationToken); + } + + private async Task ShowStartAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + "Start menu:".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Account.GetValue()}. \t{MenuTypes.Account.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Leaderboard.GetValue()}. \t{MenuTypes.Leaderboard.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Exit.GetValue()}. \t{MenuTypes.Exit.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + + "Please select an item from the list".Print(ConsoleColor.Green); + + Console.Write("Select -> "); + + var menuType = Console.ReadLine().TryGetMenuType(); + + if (menuType is MenuTypes.Unknown) + { + "Invalid input. Try again.".Print(ConsoleColor.Red); + + continue; + } + + switch (menuType) + { + case MenuTypes.Account: + await _accountMenu.StartAsync(cancellationToken); + Console.Clear(); + + break; + + case MenuTypes.Leaderboard: + await _statisticsMenu.StartAsync(cancellationToken); + + break; + + case MenuTypes.Exit: + await _accountMenu.LogoutAsync(cancellationToken); + + return; + + default: + "Invalid input. Try again.".Print(ConsoleColor.Red); + continue; + } + } + } + + private static void PrintGreeting() + { + ("VERSION 2.0\n" + + "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + + "You are given the opportunity to compete with other users in this wonderful game,\n" + + "or if you don’t have anyone to play, don’t worry,\n" + + "you can find a random player or just try your skill with a bot.").Print(ConsoleColor.White); + + "(c)Ihor Volokhovych & Michael Terekhov".Print(ConsoleColor.Cyan); + } +} \ No newline at end of file diff --git a/Client/Client.Statistics/Client.Statistics.csproj b/Client/Client.Statistics/Client.Statistics.csproj new file mode 100644 index 0000000..bf5f454 --- /dev/null +++ b/Client/Client.Statistics/Client.Statistics.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/Client/Client.Statistics/Enums/MenuTypes.cs b/Client/Client.Statistics/Enums/MenuTypes.cs new file mode 100644 index 0000000..843803c --- /dev/null +++ b/Client/Client.Statistics/Enums/MenuTypes.cs @@ -0,0 +1,9 @@ +namespace Client.Statistics.Enums; + +internal enum MenuTypes +{ + Unknown = 0, + Personal = 1, + All = 2, + Back = 3, +} \ No newline at end of file diff --git a/Client/Client.Statistics/Extensions/EnumExtensions.cs b/Client/Client.Statistics/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..e57576f --- /dev/null +++ b/Client/Client.Statistics/Extensions/EnumExtensions.cs @@ -0,0 +1,39 @@ +using Client.Statistics.Enums; + +namespace Client.Statistics.EnumExtensions; + +internal static class EnumExtensions +{ + internal static string GetDisplayName(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Back => "Back", + MenuTypes.All => "Top 10 Users", + MenuTypes.Personal => "Personal statistics", + _ => "Unknown", + }; + } + + internal static MenuTypes TryGetMenuType(this string? stringInput) + { + return stringInput switch + { + "3" => MenuTypes.Back, + "2" => MenuTypes.All, + "1" => MenuTypes.Personal, + _ => MenuTypes.Unknown, + }; + } + + internal static int GetValue(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Back => 3, + MenuTypes.All => 2, + MenuTypes.Personal => 1, + _ => 0, + }; + } +} \ No newline at end of file diff --git a/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs b/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f0d24fa --- /dev/null +++ b/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using Client.Account.Services; +using Client.Statistics.Menus; +using Client.Statistics.Services; +using RockPaperScissors.Common.Client; + +namespace Client.Statistics.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IStatisticsService CreateStatisticsService(this IClient client) + { + return new StatisticsService(client); + } + + public static IStatisticsMenu CreateStatisticsMenu(this IStatisticsService statisticsService, IAccountService accountService) + { + return new StatisticsMenu(statisticsService, accountService); + } +} \ No newline at end of file diff --git a/Client/Client.Statistics/Menus/IStatisticsMenu.cs b/Client/Client.Statistics/Menus/IStatisticsMenu.cs new file mode 100644 index 0000000..c7b7ebe --- /dev/null +++ b/Client/Client.Statistics/Menus/IStatisticsMenu.cs @@ -0,0 +1,6 @@ +namespace Client.Statistics.Menus; + +public interface IStatisticsMenu +{ + Task StartAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Client/Client.Statistics/Menus/StatisticsMenu.cs b/Client/Client.Statistics/Menus/StatisticsMenu.cs new file mode 100644 index 0000000..1fa269d --- /dev/null +++ b/Client/Client.Statistics/Menus/StatisticsMenu.cs @@ -0,0 +1,153 @@ +using Client.Account.Services; +using Client.Statistics.EnumExtensions; +using Client.Statistics.Enums; +using Client.Statistics.Services; +using RockPaperScissors.Common.Extensions; +using RockPaperScissors.Common.Responses; + +namespace Client.Statistics.Menus; + +internal sealed class StatisticsMenu: IStatisticsMenu +{ + private readonly IStatisticsService _statisticsService; + private readonly IAccountService _accountService; + + public StatisticsMenu(IStatisticsService statisticsService, IAccountService accountService) + { + _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); + _accountService = accountService ?? throw new ArgumentNullException(nameof(accountService)); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + "Statistics Menu:".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.All.GetValue()}.\t{MenuTypes.All.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Personal.GetValue()}.\t{MenuTypes.Personal.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.Back.GetValue()}.\t{MenuTypes.Back.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + + "\nPlease select an item from the list".Print(ConsoleColor.Green); + + Console.Write("Select -> "); + + var menuType = Console.ReadLine().TryGetMenuType(); + + if (menuType is MenuTypes.Unknown) + { + "Invalid input. Try again.".Print(ConsoleColor.Red); + + continue; + } + + switch (menuType) + { + case MenuTypes.All: + await PrintAllStatisticsAsync(cancellationToken); + + break; + + case MenuTypes.Personal: + await PrintPersonalStatisticsAsync(cancellationToken); + + break; + + case MenuTypes.Back: + return; + + default: + "Invalid input. Try again.".Print(ConsoleColor.Red); + continue; + } + } + } + + private async Task PrintAllStatisticsAsync(CancellationToken cancellationToken) + { + var allStatistics = await _statisticsService.GetAllAsync(cancellationToken); + + if (allStatistics.IsT0) + { + PrintAllStatistics(allStatistics.AsT0); + + return; + } + + allStatistics.AsT1.Message.Print(ConsoleColor.Red); + } + + private async Task PrintPersonalStatisticsAsync(CancellationToken cancellationToken) + { + if (!_accountService.IsAuthorized()) + { + "User in not logged in.".Print(ConsoleColor.Red); + + return; + } + + var user = _accountService.GetUser(); + var personalStatistics = await _statisticsService.GetPersonalAsync(user.SessionId, cancellationToken); + + if (personalStatistics.IsT0) + { + PrintStatistics(personalStatistics.AsT0, user.Login!); + + return; + } + + personalStatistics.AsT1.Message.Print(ConsoleColor.Red); + } + + private static void PrintAllStatistics(AllStatisticsResponse[] allStatistics) + { + var statisticsSpan = allStatistics.AsSpan(); + + for (var index = 0; index < statisticsSpan.Length; index++) + { + var color = GetColor(index); + $"{index + 1}. User: {statisticsSpan[index].Login}; Score: {statisticsSpan[index].Score}".Print(color); + } + } + + private static void PrintStatistics(PersonalStatisticsResponse allStatistics, string userName) + { + $"Here is your statistics, {userName}:".Print(ConsoleColor.White); + + "Main statistics: ".Print(ConsoleColor.DarkYellow); + + $"\t{nameof(allStatistics.Wins)}: {allStatistics.Wins}".Print(ConsoleColor.Green); + $"\t{nameof(allStatistics.Draws)}: {allStatistics.Draws}".Print(ConsoleColor.Yellow); + $"\t{nameof(allStatistics.Loss)}: {allStatistics.Loss}".Print(ConsoleColor.Red); + $"\t{nameof(allStatistics.Score)}: {allStatistics.Score}".Print(ConsoleColor.White); + + "Other statistics: ".Print(ConsoleColor.DarkYellow); + + $"\t{allStatistics.TimeSpent} spent time playing".Print(ConsoleColor.White); + $"\t{allStatistics.UsedPaper} times used 'Paper'".Print(ConsoleColor.White); + $"\t{allStatistics.UsedRock} times used 'Rock'".Print(ConsoleColor.White); + $"\t{allStatistics.UsedScissors} times used 'Scissors'".Print(ConsoleColor.White); + $"\t{allStatistics.WinLossRatio}% Win/Loss ratio".Print(GetColor(allStatistics.WinLossRatio)); + } + + private static ConsoleColor GetColor(int index) + { + return index switch + { + < 3 => ConsoleColor.Green, + < 6 => ConsoleColor.Yellow, + _ => ConsoleColor.White + }; + } + + private static ConsoleColor GetColor(double index) + { + return index switch + { + > 75d => ConsoleColor.DarkGreen, + > 50d => ConsoleColor.Green, + > 25d => ConsoleColor.Yellow, + > 10d => ConsoleColor.Red, + _ => ConsoleColor.DarkRed + }; + } +} \ No newline at end of file diff --git a/Client/Client.Statistics/Services/IStatisticsService.cs b/Client/Client.Statistics/Services/IStatisticsService.cs new file mode 100644 index 0000000..aa4fcbe --- /dev/null +++ b/Client/Client.Statistics/Services/IStatisticsService.cs @@ -0,0 +1,14 @@ +using OneOf; +using RockPaperScissors.Common; +using RockPaperScissors.Common.Responses; + +namespace Client.Statistics.Services; + +public interface IStatisticsService +{ + Task> GetAllAsync(CancellationToken cancellationToken); + + Task> GetPersonalAsync( + string? token, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Client/Client.Statistics/Services/StatisticsService.cs b/Client/Client.Statistics/Services/StatisticsService.cs new file mode 100644 index 0000000..7de64cf --- /dev/null +++ b/Client/Client.Statistics/Services/StatisticsService.cs @@ -0,0 +1,34 @@ +using OneOf; +using RockPaperScissors.Common; +using RockPaperScissors.Common.Client; +using RockPaperScissors.Common.Responses; + +namespace Client.Statistics.Services; + +internal sealed class StatisticsService: IStatisticsService +{ + private readonly IClient _client; + + public StatisticsService(IClient client) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public Task> GetAllAsync(CancellationToken cancellationToken) + { + var response = + _client.GetAsync("api/Statistics/all", cancellationToken); + + return response; + } + + public Task> GetPersonalAsync(string? token, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(token)) + { + return Task.FromResult(OneOf.FromT1(new CustomException("Token must not be 'null' or '\"\"'"))); + } + + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Client/Client.csproj b/Client/Client.csproj deleted file mode 100644 index a2c80ad..0000000 --- a/Client/Client.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net6 - - - - - - - - - - - diff --git a/Client/Extensions/JsonExtension.cs b/Client/Extensions/JsonExtension.cs deleted file mode 100644 index d7c60ec..0000000 --- a/Client/Extensions/JsonExtension.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Client.Services.RequestProcessor.RequestModels; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Schema; - -namespace Client.Extensions; - -public static class JsonExtension -{ - private const string Schema = @"{ -'code': {'type': 'integer'}, -'message': {'type': 'string'}, -}"; - - private static JSchema JSchema => JSchema.Parse(Schema); - - public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() - { - if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) - { - deserialized = default; - return false; - } - - var jObject = JObject.Parse(json.Content); - - var isValid = jObject.IsValid(JSchema); - - if (!isValid) - { - deserialized = default; - return false; - } - - deserialized = JsonConvert.DeserializeObject(json.Content); - return true; - } -} \ No newline at end of file diff --git a/Client/Menus/AccountMenu.cs b/Client/Menus/AccountMenu.cs deleted file mode 100644 index 69ffba3..0000000 --- a/Client/Menus/AccountMenu.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Client.Extensions; -using Client.Models; -using Client.Services; -using Client.Services.RequestProcessor; -using Client.Services.RequestProcessor.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; -using Newtonsoft.Json; - -namespace Client.Menus; - -public class AccountMenu : IAccountMenu -{ - private readonly IRequestPerformer _performer; - - public AccountMenu(IRequestPerformer performer) - { - _performer = performer; - } - - public async Task RegisterAsync() - { - TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + - "Please enter the required details\n" + - "to register an account on the platform", ConsoleColor.Magenta); - var registrationAccount = new Account - { - Login = new StringPlaceholder().BuildString("Login"), - Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password") - }; - - var options = new RequestOptions - { - ContentType = "application/json", - Body = JsonConvert.SerializeObject(registrationAccount), - Address = "account/register", - IsValid = true, - Method = RequestMethod.Post, - Name = "Registration" - }; - var reachedResponse = await _performer.PerformRequestAsync(options); - - if (reachedResponse.TryParseJson(out var errorModel)) - { - TextWrite.Print(errorModel.Message, ConsoleColor.Red); - return false; - } - - TextWrite.Print("Successfully registered!", ConsoleColor.Green); - return true; - } - - public async Task<(string token, TokenModel inputAccount)> LoginAsync() - { - var inputAccount = new Account - { - Login = new StringPlaceholder().BuildString("Login"), - Password = - new StringPlaceholder(StringDestination.Password).BuildString("Password", true) - }; - var options = new RequestOptions - { - ContentType = "application/json", - Body = JsonConvert.SerializeObject(inputAccount), - Address = "account/login", - IsValid = true, - Method = RequestMethod.Post, - Name = "Login" - }; - var reachedResponse = await _performer.PerformRequestAsync(options); - - if (reachedResponse.TryParseJson(out var tokenModel)) - { - TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); - return (tokenModel.Token, tokenModel); - } - - var error = JsonConvert.DeserializeObject(reachedResponse.Content); - if(error is null) return (null, null); - - TextWrite.Print(error.Message, ConsoleColor.Red); - return (null, null); - } - - public async Task LogoutAsync(string token) - { - var options = new RequestOptions - { - Headers = new Dictionary{{"Authorization",token}}, - Address = $"user/logout/{token}", - IsValid = true, - Method = RequestMethod.Get - }; - await _performer.PerformRequestAsync(options); - TextWrite.Print("Successfully signed out", ConsoleColor.Green); - return true; - } -} \ No newline at end of file diff --git a/Client/Menus/IAccountMenu.cs b/Client/Menus/IAccountMenu.cs deleted file mode 100644 index 71532a0..0000000 --- a/Client/Menus/IAccountMenu.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using Client.Models; - -namespace Client.Menus; - -public interface IAccountMenu -{ - Task RegisterAsync(); - - Task<(string token, TokenModel inputAccount)> LoginAsync(); - - Task LogoutAsync(string token); -} \ No newline at end of file diff --git a/Client/Menus/StartMenu.cs b/Client/Menus/StartMenu.cs deleted file mode 100644 index a90ca9f..0000000 --- a/Client/Menus/StartMenu.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Client.Models; -using Client.Services; -using Client.Services.RequestProcessor; - -namespace Client.Menus; - -public class StartMenu -{ - private readonly IAccountMenu _accountMenu; - private readonly IStatisticsService _statisticsService; - private readonly IRequestPerformer _requestPerformer; - - private string SessionId { get; set; } - - public StartMenu(IRequestPerformer performer) - { - _requestPerformer = performer; - _statisticsService = new StatisticsService(performer); - _accountMenu = new AccountMenu(performer); - } - - public async Task StartAsync() - { - var tokenSource = new CancellationTokenSource(); - var token = tokenSource.Token; - - await Greeting().ConfigureAwait(false); - TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); - - Console.ReadKey(); - Console.Clear(); - //todo: trying to connect to the server - await Menu(token); - return 1; - } - private async Task Menu(CancellationToken token) - { - while (true) - { - TextWrite.Print("Start menu:\n" + - "1.\tSign up\n" + - "2.\tLog in\n" + - "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics - "4.\tExit", ConsoleColor.DarkYellow); - - TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); - - Console.Write("Select -> "); - if (token.IsCancellationRequested) - { - return; - } - var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); - if (!passed) - { - TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); - continue; - } - switch (startMenuInput) - { - case 1: - await _accountMenu.RegisterAsync(); - TextWrite.Print( - "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); - Console.ReadKey(); - Console.Clear(); - break; - case 2: - TokenModel inputAccount; - (SessionId, inputAccount) = await _accountMenu.LoginAsync(); - if (!string.IsNullOrEmpty(SessionId)) - { - Console.Clear(); - await new MainMenu(inputAccount,_requestPerformer,_statisticsService) - .PlayerMenu(); - } - Console.Clear(); - break; - case 3: - var result = await _statisticsService.GetAllStatistics(); - await _statisticsService.PrintStatistics(result); - /*var statistics = await OverallStatistics(); - if(statistics == null) - Console.WriteLine("No statistics so far"); - else - { - PrintStatistics(statistics); - }*/ - break; - case 4: - if (await _accountMenu.LogoutAsync(SessionId)) - { - Console.WriteLine("DEBUG: Logged out"); - return; - } - else - { - throw new NotImplementedException(); - } - default: - TextWrite.Print("Unsupported input", ConsoleColor.Red); - continue; - } - } - } - - private static Task Greeting() - { - TextWrite.Print( - "VERSION 2.0\n" + - "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + - "You are given the opportunity to compete with other users in this wonderful game,\n" + - "or if you don’t have anyone to play, don’t worry,\n" + - "you can find a random player or just try your skill with a bot.", ConsoleColor.White); - TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Client/Models/Account.cs b/Client/Models/Account.cs deleted file mode 100644 index 4b39ecb..0000000 --- a/Client/Models/Account.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Client.Models.Interfaces; -using Newtonsoft.Json; - -namespace Client.Models; - -public class Account : IAccount -{ - [JsonProperty("Login")] - public string Login { get; set; } - - [JsonProperty("Password")] - public string Password { get; set; } -} \ No newline at end of file diff --git a/Client/Models/ErrorModel.cs b/Client/Models/ErrorModel.cs deleted file mode 100644 index 9d761ac..0000000 --- a/Client/Models/ErrorModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Newtonsoft.Json; - -namespace Client.Models; - -public class ErrorModel -{ - [JsonProperty("Code")] - public int Code { get; set; } - [JsonProperty("Message")] - public string Message { get; set; } -} \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs deleted file mode 100644 index e11d105..0000000 --- a/Client/Program.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Client.Services.RequestProcessor.Impl; -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Client.Menus; - -namespace Client; - -internal static class Program -{ - private static async Task Main() - { - try - { - var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; - var clientHandler = new HttpClientHandler(); - var requestHandler = new RequestHandler(client, clientHandler); - var requestPerformer = new RequestPerformer(requestHandler); - - var startMenu = new StartMenu(requestPerformer); - return await startMenu.StartAsync(); - } - catch (Exception) //todo : do this need a message? - { - Console.WriteLine("Unknown error occured. Crash."); - return -1; - } - } -} \ No newline at end of file diff --git a/Client/Services/RoomService.cs b/Client/Services/RoomService.cs deleted file mode 100644 index cfe9336..0000000 --- a/Client/Services/RoomService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Client.Extensions; -using Client.Models; -using Client.Services.RequestProcessor; -using Client.Services.RequestProcessor.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; -using Newtonsoft.Json; - -namespace Client.Services; - -public class RoomService : IRoomService -{ - private readonly TokenModel _tokenModel; - private readonly IRequestPerformer _requestPerformer; - - public RoomService(TokenModel tokenModel, - IRequestPerformer requestPerformer) - { - _tokenModel = tokenModel; - _requestPerformer = requestPerformer; - } - public async Task CreateRoom(bool isPrivate, bool isTraining) - { - Console.WriteLine("Trying to create a room."); - - var options = new RequestOptions - { - Address = $"room/create?isPrivate={isPrivate}", - IsValid = true, - Headers = new Dictionary - { - { - "Authorization", _tokenModel.BearerToken - }, - { - "X-Training", isTraining.ToString() - } - }, - Method = RequestMethod.Post, - Body = string.Empty, - Name = "Create Room" - }; - var reachedResponse = await _requestPerformer - .PerformRequestAsync(options); - - if (reachedResponse.TryParseJson(out var errorModel)) - { - TextWrite.Print(errorModel.Message, ConsoleColor.Red); - return null; - } - - var room = JsonConvert.DeserializeObject(reachedResponse.Content); - return room; - } -} - -public interface IRoomService -{ - Task CreateRoom(bool isPrivate, bool isTraining); -} \ No newline at end of file diff --git a/Client/Services/StatisticsService.cs b/Client/Services/StatisticsService.cs deleted file mode 100644 index c9e73c6..0000000 --- a/Client/Services/StatisticsService.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Client.Models; -using Client.Models.Interfaces; -using Client.Services.RequestProcessor; -using Client.Services.RequestProcessor.RequestModels; -using Client.Services.RequestProcessor.RequestModels.Impl; -using Mapster; -using Newtonsoft.Json; - -namespace Client.Services; - -public class StatisticsService: IStatisticsService -{ - private readonly IRequestPerformer _requestPerformer; - - public StatisticsService(IRequestPerformer requestPerformer) - { - _requestPerformer = requestPerformer; - } - - public async Task GetAllStatistics() - { - var options = new RequestOptions - { - ContentType = "none", - Address = "stats/all", - IsValid = true, - Method = RequestMethod.Get, - Name = "OverallStats" - }; - - var response = await _requestPerformer.PerformRequestAsync(options); - - var toConvert = JsonConvert.DeserializeObject(response.Content); - return toConvert?.Adapt(); - } - - public async Task GetPersonalStatistics(string token) - { - var options = new RequestOptions - { - Headers = new Dictionary{{"Authorization",token}}, - ContentType = "none", - Address = "stats/personal", - IsValid = true, - Method = RequestMethod.Get, - Name = "PersonalStats" - }; - - var response = await _requestPerformer.PerformRequestAsync(options); - - return response.Content != null ? JsonConvert.DeserializeObject(response.Content) : null; - } - - public Task PrintStatistics(IOverallStatistics[] statistics) - { - for(var i = 0; i < statistics.Length; i++) - { - TextWrite.Print($"{i+1}. User: {statistics[i].Account.Login}\n" + - $"Score: {statistics[i].Score}", ConsoleColor.White); - } - Console.Write('\n'); - return Task.CompletedTask; - } -} - -public interface IStatisticsService -{ - Task GetAllStatistics(); - Task GetPersonalStatistics(string token); - Task PrintStatistics(IOverallStatistics[] statistics); -} \ No newline at end of file diff --git a/Client/Services/StringPlaceholder.cs b/Client/Services/StringPlaceholder.cs deleted file mode 100644 index db2f8f6..0000000 --- a/Client/Services/StringPlaceholder.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using Client.Models; -using Client.Validations; - -namespace Client.Services; - -internal class StringPlaceholder -{ - private readonly StringDestination _destination; - public StringPlaceholder() - { - _destination = StringDestination.Login; - } - public StringPlaceholder(StringDestination destination) - { - _destination = destination; - } - public string BuildString(string msg, bool isNeedConfirmation = false) - { - string output; - while (true) - { - var passwordNotConfirmed = true; - TextWrite.Print( - _destination is StringDestination.PassportType or StringDestination.Email - ? $"What is your {msg}?" - : $"Try to come up with {msg}?", ConsoleColor.Yellow); - Console.Write($"{msg}--> "); - - output = Console.ReadLine() - ?.Trim() - .Replace(" ", ""); - if (string.IsNullOrEmpty(output)) - { - TextWrite.Print("Wrong data!", ConsoleColor.Red); - continue; - } - switch (_destination) - { - case StringDestination.Password when output.Length < 6: - TextWrite.Print("Wrong password length!", ConsoleColor.Red); - continue; - case StringDestination.Email when !StringValidator.IsEmailValid(output): - TextWrite.Print("This email is not valid!", ConsoleColor.Red); - continue; - } - - if (_destination == StringDestination.Password) - { - if (isNeedConfirmation) - break; - TextWrite.Print("You need to confirm password!", ConsoleColor.Yellow); - do - { - Console.Write("Confirmation--> "); - var confirmationPassword = Console.ReadLine() - ?.Trim() - .Replace(" ", ""); - if (string.IsNullOrEmpty(output)) - { - TextWrite.Print("Wrong data!", ConsoleColor.Red); - continue; - } - if (output == confirmationPassword) - { - TextWrite.Print("Password confirmed", ConsoleColor.Green); - passwordNotConfirmed = false; - } - else - TextWrite.Print("Passwords dont match!",ConsoleColor.Red); - } while (passwordNotConfirmed); - } - if (_destination == StringDestination.PassportType && StringValidator.IsStringContainsDigits(output)) - { - TextWrite.Print("You cannot enter nameType with digits!", ConsoleColor.Red); - continue; - } - break; - } - return output; - } -} \ No newline at end of file diff --git a/Client/Services/TextWrite.cs b/Client/Services/TextWrite.cs deleted file mode 100644 index cd60c41..0000000 --- a/Client/Services/TextWrite.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Client.Services; - -public static class TextWrite -{ - public static void Print(string msg, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.WriteLine(msg); - Console.ResetColor(); - } -} \ No newline at end of file diff --git a/Client/Validations/StringValidator.cs b/Client/Validations/StringValidator.cs deleted file mode 100644 index 83459f2..0000000 --- a/Client/Validations/StringValidator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Client.Validations; - -public static class StringValidator -{ - public static bool IsStringContainsDigits(string str) - { - var res = str.Any(ch => Char.IsDigit(ch)); - return res; - } - public static bool IsEmailValid(string email) - { - var emailPattern = "[.\\-_a-z0-9]+@([a-z0-9][\\-a-z0-9]+\\.)+[a-z]{2,6}"; - var match = Regex.Match(email,emailPattern,RegexOptions.IgnoreCase); - return match.Success; - } -} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Client/Client.cs b/Common/RockPaperScissors.Common/Client/Client.cs new file mode 100644 index 0000000..e692ec7 --- /dev/null +++ b/Common/RockPaperScissors.Common/Client/Client.cs @@ -0,0 +1,144 @@ +using System.Net; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using OneOf; + +namespace RockPaperScissors.Common.Client; + +public sealed class Client : IClient +{ + private static readonly HttpClient HttpClient = new() + { + BaseAddress = null, + DefaultRequestVersion = HttpVersion.Version20, + DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower, + DefaultRequestHeaders = + { + {"Accept", MediaTypeNames.Application.Json} + } + }; + + private Client(string baseAddress) + { + HttpClient.BaseAddress = new Uri(baseAddress); + } + + public static Client Create(string baseAddress) => new(baseAddress); + + public async Task> GetAsync( + string url, + CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(HttpClient.BaseAddress!, url), + Version = HttpClient.DefaultRequestVersion, + VersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + using var response = + await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + public async Task> GetAsync( + string url, + (string HeaderKey, string HeaderValue) headerValues, + CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + Headers = { + { + headerValues.HeaderKey, headerValues.HeaderValue + } + }, + RequestUri = new Uri(HttpClient.BaseAddress!, url), + Version = HttpClient.DefaultRequestVersion, + VersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + using var response = + await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + public async Task> PostAsync( + string url, + T1 content, + CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage + { + Method = HttpMethod.Post, + Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, MediaTypeNames.Application.Json), + RequestUri = new Uri(HttpClient.BaseAddress!, url), + Version = HttpClient.DefaultRequestVersion, + VersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + using var response = + await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + public async Task> PostAsync( + string url, + T1 content, + (string HeaderKey, string HeaderValue) headerValues, + CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage + { + Method = HttpMethod.Post, + Headers = { + { + headerValues.HeaderKey, headerValues.HeaderValue + } + }, + Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, MediaTypeNames.Application.Json), + RequestUri = new Uri(HttpClient.BaseAddress!, url), + Version = HttpClient.DefaultRequestVersion, + VersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + using var response = + await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } + + return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; + } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Client/IClient.cs b/Common/RockPaperScissors.Common/Client/IClient.cs new file mode 100644 index 0000000..f994ce8 --- /dev/null +++ b/Common/RockPaperScissors.Common/Client/IClient.cs @@ -0,0 +1,25 @@ +using OneOf; + +namespace RockPaperScissors.Common.Client; + +public interface IClient +{ + Task> GetAsync( + string url, + CancellationToken cancellationToken = default); + + Task> GetAsync( + string url, + (string HeaderKey, string HeaderValue) headerValues, + CancellationToken cancellationToken = default); + + Task> PostAsync( + string url, + T1 content, + CancellationToken cancellationToken = default); + + Task> PostAsync( + string url, T1 content, + (string HeaderKey, string HeaderValue) headerValues, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/CustomException.cs b/Common/RockPaperScissors.Common/CustomException.cs new file mode 100644 index 0000000..a1fdba4 --- /dev/null +++ b/Common/RockPaperScissors.Common/CustomException.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace RockPaperScissors.Common; + +public sealed class CustomException +{ + [JsonConstructor] + public CustomException(int code, string message) + { + Message = message; + Code = code; + } + + public CustomException(string message) + { + Message = message; + Code = 400; + } + + [JsonPropertyName("code")] + public int Code { get; init; } + + [JsonPropertyName("message")] + public string Message { get; init; } +} \ No newline at end of file diff --git a/Client/Models/StringDestination.cs b/Common/RockPaperScissors.Common/Enums/StringDestination.cs similarity index 75% rename from Client/Models/StringDestination.cs rename to Common/RockPaperScissors.Common/Enums/StringDestination.cs index 2a0c63d..3dec296 100644 --- a/Client/Models/StringDestination.cs +++ b/Common/RockPaperScissors.Common/Enums/StringDestination.cs @@ -1,4 +1,4 @@ -namespace Client.Models; +namespace RockPaperScissors.Common.Enums; public enum StringDestination { diff --git a/Common/RockPaperScissors.Common/Extensions/QueryBuilder.cs b/Common/RockPaperScissors.Common/Extensions/QueryBuilder.cs new file mode 100644 index 0000000..adaa602 --- /dev/null +++ b/Common/RockPaperScissors.Common/Extensions/QueryBuilder.cs @@ -0,0 +1,133 @@ +using System.Buffers; +using System.Collections; + +namespace RockPaperScissors.Common.Extensions; + +public sealed class QueryBuilder : IReadOnlyCollection> +{ + private readonly IList> _valuePairs; + + /// + public int Count { get; private set; } + + /// + /// Constructor. + /// + public QueryBuilder() + { + _valuePairs = new List>(); + } + + /// + /// Constructor. + /// + public QueryBuilder(int count) + { + Count = count; + _valuePairs = new List>(count); + } + + /// + /// Constructor. + /// + /// Collection of KeyValuePairs. + public QueryBuilder(IReadOnlyCollection> parameters) + { + Count += parameters.Sum(pair => pair.Key.Length + pair.Value.Length); + + _valuePairs = new List>(parameters); + } + + /// + /// Adds key and it's values to the collection. + /// + /// Key. + /// Collection of values. + public void Add(string key, IEnumerable values) + { + Count += key.Length; + + foreach (var value in values) + { + Count += value.Length; + + _valuePairs.Add(KeyValuePair.Create(key, value)); + } + } + + /// + /// Adds key and it's value to the collection. + /// + /// Key. + /// Value. + public void Add(string key, string? value) + { + Count += key.Length + value.Length; + + _valuePairs.Add(KeyValuePair.Create(key, value)); + } + + /// + public override string ToString() + { + if (Count is 0) + { + return string.Empty; + } + + var queryLength = Count * 2; + + var isStackAlloc = queryLength <= 64; + var currentPosition = 0; + + var array = isStackAlloc ? null : ArrayPool.Shared.Rent(queryLength); + var resultSpan = isStackAlloc ? stackalloc char[queryLength] : array; + + try + { + var first = true; + for (var i = 0; i < _valuePairs.Count; i++) + { + var pair = _valuePairs[i]; + resultSpan[currentPosition] = first ? '?' : '&'; + first = false; + currentPosition++; + + var escapeKey = Uri.EscapeDataString(pair.Key); + escapeKey.CopyTo(resultSpan[currentPosition..]); + currentPosition += escapeKey.Length; + + resultSpan[currentPosition++] = '='; + + var escapedValue = Uri.EscapeDataString(pair.Value); + escapedValue.CopyTo(resultSpan[currentPosition..]); + currentPosition += escapedValue.Length; + } + + var endIndex = resultSpan.IndexOf('\0'); + + return endIndex is -1 + ? resultSpan[..currentPosition].ToString() + : resultSpan[..(endIndex > currentPosition ? currentPosition : endIndex)].ToString(); + } + finally + { + if (array is not null) + { + ArrayPool.Shared.Return(array); + } + } + } + + /// + public IEnumerator> GetEnumerator() + { + return _valuePairs.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _valuePairs.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs b/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs new file mode 100644 index 0000000..f9ff470 --- /dev/null +++ b/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs @@ -0,0 +1,97 @@ +using System.Text.RegularExpressions; +using RockPaperScissors.Common.Enums; + +namespace RockPaperScissors.Common.Extensions; + +public static class TextWriteExtensions +{ + private static readonly Regex EmailRegex = + new("[.\\-_a-z0-9]+@([a-z0-9][\\-a-z0-9]+\\.)+[a-z]{2,6}", RegexOptions.Compiled); + + public static void Print(this string message, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.WriteLine(message); + Console.ResetColor(); + } + + public static string BuildString(this string msg, StringDestination destination, bool isNeedConfirmation = false) + { + string output; + while (true) + { + var passwordNotConfirmed = true; + Print( + destination is StringDestination.PassportType or StringDestination.Email + ? $"What is your {msg}?" + : $"Try to come up with {msg}?", ConsoleColor.Yellow); + Console.Write($"{msg}--> "); + + output = Console.ReadLine() + ?.Trim() + ?.Replace(" ", ""); + if (string.IsNullOrEmpty(output)) + { + Print("Wrong data!", ConsoleColor.Red); + continue; + } + switch (destination) + { + case StringDestination.Password when output.Length < 6: + Print("Wrong password length!", ConsoleColor.Red); + continue; + case StringDestination.Email when !IsEmailValid(output): + Print("This email is not valid!", ConsoleColor.Red); + continue; + } + + if (destination == StringDestination.Password) + { + if (isNeedConfirmation) + break; + Print("You need to confirm password!", ConsoleColor.Yellow); + do + { + Console.Write("Confirmation--> "); + var confirmationPassword = Console.ReadLine() + ?.Trim() + .Replace(" ", ""); + if (string.IsNullOrEmpty(output)) + { + Print("Wrong data!", ConsoleColor.Red); + continue; + } + if (output == confirmationPassword) + { + Print("Password confirmed", ConsoleColor.Green); + passwordNotConfirmed = false; + } + else + Print("Passwords dont match!",ConsoleColor.Red); + } while (passwordNotConfirmed); + } + if (destination is StringDestination.PassportType && ContainsDigits(output)) + { + Print("You cannot enter nameType with digits!", ConsoleColor.Red); + continue; + } + break; + } + + return output; + } + + public static bool ContainsDigits(this string str) + { + var res = str.Any(character => char.IsDigit(character)); + + return res; + } + + public static bool IsEmailValid(this string email) + { + var match = EmailRegex.Match(email); + + return match.Success; + } +} \ No newline at end of file diff --git a/Server.Host/Contracts/AccountDto.cs b/Common/RockPaperScissors.Common/Models/AccountDto.cs similarity index 70% rename from Server.Host/Contracts/AccountDto.cs rename to Common/RockPaperScissors.Common/Models/AccountDto.cs index 1eb912b..f3c0f92 100644 --- a/Server.Host/Contracts/AccountDto.cs +++ b/Common/RockPaperScissors.Common/Models/AccountDto.cs @@ -1,12 +1,15 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; -namespace Server.Host.Contracts; +namespace RockPaperScissors.Common.Models; public sealed class AccountDto { + [JsonPropertyName("login")] [Required(ErrorMessage = "Login is required!")] public string Login { get; init; } + [JsonPropertyName("password")] [Required(ErrorMessage = "Password is required!")] [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length")] public string Password { get; init;} diff --git a/Server.Host/Contracts/StatisticsDto.cs b/Common/RockPaperScissors.Common/Models/StatisticsDto.cs similarity index 60% rename from Server.Host/Contracts/StatisticsDto.cs rename to Common/RockPaperScissors.Common/Models/StatisticsDto.cs index 456df7d..b0a60f1 100644 --- a/Server.Host/Contracts/StatisticsDto.cs +++ b/Common/RockPaperScissors.Common/Models/StatisticsDto.cs @@ -1,12 +1,12 @@ using System.Text.Json.Serialization; -namespace Server.Host.Contracts; +namespace RockPaperScissors.Common.Models; public sealed class StatisticsDto { - [JsonPropertyName("Login")] + [JsonPropertyName("login")] public string Login { get; init; } - [JsonPropertyName("Score")] + [JsonPropertyName("score")] public int Score { get; init; } } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/LoginRequest.cs b/Common/RockPaperScissors.Common/Requests/LoginRequest.cs similarity index 59% rename from Server.Host/Contracts/Requests/LoginRequest.cs rename to Common/RockPaperScissors.Common/Requests/LoginRequest.cs index dc97f28..71891b3 100644 --- a/Server.Host/Contracts/Requests/LoginRequest.cs +++ b/Common/RockPaperScissors.Common/Requests/LoginRequest.cs @@ -1,16 +1,19 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; -namespace Server.Host.Contracts.Requests; +namespace RockPaperScissors.Common.Requests; public sealed class LoginRequest { + [JsonPropertyName("login")] [Required(ErrorMessage = "Login is required!")] public string Login { get; init; } - + + [JsonPropertyName("password")] [Required(ErrorMessage = "Password is required!!")] [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length")] public string Password { get; init; } - + + [JsonPropertyName("lastRequestTime")] public DateTimeOffset LastRequestTime { get; init; } } \ No newline at end of file diff --git a/Server.Host/Contracts/Requests/RegisterRequest.cs b/Common/RockPaperScissors.Common/Requests/RegisterRequest.cs similarity index 52% rename from Server.Host/Contracts/Requests/RegisterRequest.cs rename to Common/RockPaperScissors.Common/Requests/RegisterRequest.cs index e334ace..171d2e4 100644 --- a/Server.Host/Contracts/Requests/RegisterRequest.cs +++ b/Common/RockPaperScissors.Common/Requests/RegisterRequest.cs @@ -1,13 +1,16 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; -namespace Server.Host.Contracts.Requests; +namespace RockPaperScissors.Common.Requests; public sealed class RegisterRequest { + [JsonPropertyName("login")] [Required(ErrorMessage = "Login is required!")] public string Login { get; init; } - + + [JsonPropertyName("password")] [Required(ErrorMessage = "Password is required!")] - [StringLength(20, MinimumLength=6, ErrorMessage = "Invalid password length. Must be 6-20")] + [StringLength(20, MinimumLength = 6, ErrorMessage = "Invalid password length. Must be 6-20")] public string Password { get; init; } } \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Responses/AllStatisticsResponse.cs b/Common/RockPaperScissors.Common/Responses/AllStatisticsResponse.cs new file mode 100644 index 0000000..c8977c2 --- /dev/null +++ b/Common/RockPaperScissors.Common/Responses/AllStatisticsResponse.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace RockPaperScissors.Common.Responses; + +public sealed class AllStatisticsResponse +{ + [JsonPropertyName("login")] + public string Login { get; init; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + [JsonPropertyName("score")] + public int Score { get; init; } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Responses/LoginResponse.cs b/Common/RockPaperScissors.Common/Responses/LoginResponse.cs new file mode 100644 index 0000000..d7d1802 --- /dev/null +++ b/Common/RockPaperScissors.Common/Responses/LoginResponse.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace RockPaperScissors.Common.Responses; + +public sealed class LoginResponse +{ + /// + /// Gets or sets user token (used in header). + /// + [JsonPropertyName("token")] + public string Token { get; init; } + + /// + /// Gets or sets user login. + /// + [JsonPropertyName("login")] + public string Login { get; init; } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Responses/PersonalStatisticsResponse.cs b/Common/RockPaperScissors.Common/Responses/PersonalStatisticsResponse.cs new file mode 100644 index 0000000..fce1513 --- /dev/null +++ b/Common/RockPaperScissors.Common/Responses/PersonalStatisticsResponse.cs @@ -0,0 +1,60 @@ +using System.Text.Json.Serialization; + +namespace RockPaperScissors.Common.Responses; + +public sealed class PersonalStatisticsResponse +{ + /// + /// Total amount of Wins + /// + [JsonPropertyName("wins")] + public int Wins { get; set; } + + /// + /// Total amount of Loses + /// + [JsonPropertyName("loss")] + public int Loss { get; set; } + + /// + /// Total amount of Draws. OBSOLETE + /// + [JsonPropertyName("draws")] + public int Draws { get; set; } + + /// + /// Ratio Wins to Losses. Win/Loss * 100 + /// + [JsonPropertyName("winLossRatio")] + public double WinLossRatio { get; set; } + + /// + /// Ratio for the last 7 days + /// + [JsonPropertyName("timeSpent")] + public string TimeSpent { get; set; } + + /// + /// Times used rock + /// + [JsonPropertyName("usedRock")] + public int UsedRock { get; set; } + + /// + /// Times used Paper + /// + [JsonPropertyName("usedPaper")] + public int UsedPaper { get; set; } + + /// + /// Times used Scissors + /// + [JsonPropertyName("usedScissors")] + public int UsedScissors { get; set; } + + /// + /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. + /// + [JsonPropertyName("score")] + public int Score { get; set; } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/RockPaperScissors.Common.csproj b/Common/RockPaperScissors.Common/RockPaperScissors.Common.csproj new file mode 100644 index 0000000..09d3722 --- /dev/null +++ b/Common/RockPaperScissors.Common/RockPaperScissors.Common.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index b72ab77..921fd26 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -3,19 +3,27 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Host", "Server.Host\Server.Host.csproj", "{53166F83-4032-4CCF-B172-C2517BFBA424}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{08EA7A60-9BAA-4B5F-B6EA-21A68328B2F6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{B6D69162-F9E0-4165-A288-00FF1D073291}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{540EE2A5-907D-4E6A-A051-22D01DBFE913}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Data", "Server.Data\Server.Data.csproj", "{1207E8F7-D6FF-4F7D-B5A1-55D96F286430}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Host", "Server\Server.Host\Server.Host.csproj", "{DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server.Bll\Server.Bll.csproj", "{FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Data", "Server\Server.Data\Server.Data.csproj", "{BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Authentication", "Server.Authentication\Server.Authentication.csproj", "{004A30E1-2903-4C9F-8D02-EEBE67C677BD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Bll", "Server\Server.Bll\Server.Bll.csproj", "{74D55924-777B-4FC3-B8F5-37508B2EBAE9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{F85F3652-F3F7-4EBC-AEE5-16B7E4BB09D0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Authentication", "Server\Server.Authentication\Server.Authentication.csproj", "{1FC79883-C4C0-4C61-A3F1-D699F7A793B2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{C11A7F35-798A-4410-BFAE-CC29502DEF15}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Host", "Client\Client.Host\Client.Host.csproj", "{45AC8461-58BE-4675-AD96-89CF88CF8CA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Account", "Client\Client.Account\Client.Account.csproj", "{32045C10-4395-4010-A365-6CAF4D993680}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.StartMenu", "Client\Client.StartMenu\Client.StartMenu.csproj", "{BB2630C0-3F25-49F1-9393-4B75A7014187}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RockPaperScissors.Common", "Common\RockPaperScissors.Common\RockPaperScissors.Common.csproj", "{4E6B589E-C9C5-4191-B435-6FE4283C1B58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Statistics", "Client\Client.Statistics\Client.Statistics.csproj", "{994CC558-A9A9-4E58-A894-EE06993DAC58}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,26 +31,42 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {53166F83-4032-4CCF-B172-C2517BFBA424}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53166F83-4032-4CCF-B172-C2517BFBA424}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53166F83-4032-4CCF-B172-C2517BFBA424}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53166F83-4032-4CCF-B172-C2517BFBA424}.Release|Any CPU.Build.0 = Release|Any CPU - {B6D69162-F9E0-4165-A288-00FF1D073291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6D69162-F9E0-4165-A288-00FF1D073291}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6D69162-F9E0-4165-A288-00FF1D073291}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6D69162-F9E0-4165-A288-00FF1D073291}.Release|Any CPU.Build.0 = Release|Any CPU - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430}.Release|Any CPU.Build.0 = Release|Any CPU - {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42}.Release|Any CPU.Build.0 = Release|Any CPU - {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {004A30E1-2903-4C9F-8D02-EEBE67C677BD}.Release|Any CPU.Build.0 = Release|Any CPU + {DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9}.Release|Any CPU.Build.0 = Release|Any CPU + {BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5}.Release|Any CPU.Build.0 = Release|Any CPU + {74D55924-777B-4FC3-B8F5-37508B2EBAE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74D55924-777B-4FC3-B8F5-37508B2EBAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74D55924-777B-4FC3-B8F5-37508B2EBAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74D55924-777B-4FC3-B8F5-37508B2EBAE9}.Release|Any CPU.Build.0 = Release|Any CPU + {1FC79883-C4C0-4C61-A3F1-D699F7A793B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FC79883-C4C0-4C61-A3F1-D699F7A793B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FC79883-C4C0-4C61-A3F1-D699F7A793B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FC79883-C4C0-4C61-A3F1-D699F7A793B2}.Release|Any CPU.Build.0 = Release|Any CPU + {45AC8461-58BE-4675-AD96-89CF88CF8CA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45AC8461-58BE-4675-AD96-89CF88CF8CA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45AC8461-58BE-4675-AD96-89CF88CF8CA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45AC8461-58BE-4675-AD96-89CF88CF8CA3}.Release|Any CPU.Build.0 = Release|Any CPU + {32045C10-4395-4010-A365-6CAF4D993680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32045C10-4395-4010-A365-6CAF4D993680}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32045C10-4395-4010-A365-6CAF4D993680}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32045C10-4395-4010-A365-6CAF4D993680}.Release|Any CPU.Build.0 = Release|Any CPU + {BB2630C0-3F25-49F1-9393-4B75A7014187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB2630C0-3F25-49F1-9393-4B75A7014187}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB2630C0-3F25-49F1-9393-4B75A7014187}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB2630C0-3F25-49F1-9393-4B75A7014187}.Release|Any CPU.Build.0 = Release|Any CPU + {4E6B589E-C9C5-4191-B435-6FE4283C1B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E6B589E-C9C5-4191-B435-6FE4283C1B58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E6B589E-C9C5-4191-B435-6FE4283C1B58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E6B589E-C9C5-4191-B435-6FE4283C1B58}.Release|Any CPU.Build.0 = Release|Any CPU + {994CC558-A9A9-4E58-A894-EE06993DAC58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {994CC558-A9A9-4E58-A894-EE06993DAC58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {994CC558-A9A9-4E58-A894-EE06993DAC58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {994CC558-A9A9-4E58-A894-EE06993DAC58}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -51,10 +75,13 @@ Global SolutionGuid = {4ADC353B-52B1-43B2-B26D-3279A6ACC978} EndGlobalSection GlobalSection(NestedProjects) = preSolution - {B6D69162-F9E0-4165-A288-00FF1D073291} = {F85F3652-F3F7-4EBC-AEE5-16B7E4BB09D0} - {004A30E1-2903-4C9F-8D02-EEBE67C677BD} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} - {1207E8F7-D6FF-4F7D-B5A1-55D96F286430} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} - {FCFA76FA-5C19-4FC2-BCD7-E5B1112A5C42} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} - {53166F83-4032-4CCF-B172-C2517BFBA424} = {C11A7F35-798A-4410-BFAE-CC29502DEF15} + {DDDC0C74-C130-46DD-A0BC-1AC7FFF626F9} = {08EA7A60-9BAA-4B5F-B6EA-21A68328B2F6} + {BDF34CB3-4D51-45A3-B9BB-7DE6953B9AC5} = {08EA7A60-9BAA-4B5F-B6EA-21A68328B2F6} + {74D55924-777B-4FC3-B8F5-37508B2EBAE9} = {08EA7A60-9BAA-4B5F-B6EA-21A68328B2F6} + {1FC79883-C4C0-4C61-A3F1-D699F7A793B2} = {08EA7A60-9BAA-4B5F-B6EA-21A68328B2F6} + {45AC8461-58BE-4675-AD96-89CF88CF8CA3} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} + {32045C10-4395-4010-A365-6CAF4D993680} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} + {BB2630C0-3F25-49F1-9393-4B75A7014187} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} + {994CC558-A9A9-4E58-A894-EE06993DAC58} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} EndGlobalSection EndGlobal diff --git a/Server.Bll/Exceptions/CustomException.cs b/Server.Bll/Exceptions/CustomException.cs deleted file mode 100644 index 1ed71f7..0000000 --- a/Server.Bll/Exceptions/CustomException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Server.Bll.Exceptions; - -public sealed class CustomException -{ - public int Code { get;} - public string Message { get; } - - public CustomException(string template, int code = StatusCodes.Status400BadRequest) - { - Message = template; - Code = code; - } -} \ No newline at end of file diff --git a/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server.Bll/Services/Interfaces/IStatisticsService.cs deleted file mode 100644 index b623823..0000000 --- a/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using OneOf; -using Server.Bll.Exceptions; -using Server.Bll.Models; - -namespace Server.Bll.Services.Interfaces; - -public interface IStatisticsService -{ - Task GetAllStatistics(); - - Task> GetPersonalStatistics(string userId); -} \ No newline at end of file diff --git a/Server.Host/Account b/Server.Host/Account deleted file mode 100644 index b15c5e9..0000000 --- a/Server.Host/Account +++ /dev/null @@ -1 +0,0 @@ -{"1a047fbd-901f-464f-ab6b-e6d0f46f1558":{"Id":"1a047fbd-901f-464f-ab6b-e6d0f46f1558","Login":"string","Password":"string"},"d3441061-4c3d-43ca-8aef-b733525ecd22":{"Id":"d3441061-4c3d-43ca-8aef-b733525ecd22","Login":"123","Password":"123123"}} \ No newline at end of file diff --git a/Server.Host/Contracts/ViewModels/AccountViewModel.cs b/Server.Host/Contracts/ViewModels/AccountViewModel.cs deleted file mode 100644 index a4d7d6a..0000000 --- a/Server.Host/Contracts/ViewModels/AccountViewModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Server.Host.Contracts.ViewModels; - -public sealed class AccountViewModel -{ - public string Token { get; init; } - - public string Login { get; init; } -} \ No newline at end of file diff --git a/Server.Authentication/AuthOptions.cs b/Server/Server.Authentication/AuthOptions.cs similarity index 100% rename from Server.Authentication/AuthOptions.cs rename to Server/Server.Authentication/AuthOptions.cs diff --git a/Server.Authentication/Exceptions/UserException.cs b/Server/Server.Authentication/Exceptions/UserException.cs similarity index 100% rename from Server.Authentication/Exceptions/UserException.cs rename to Server/Server.Authentication/Exceptions/UserException.cs diff --git a/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs similarity index 100% rename from Server.Authentication/Exceptions/UserExceptionsTemplates.cs rename to Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs diff --git a/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server/Server.Authentication/Extensions/AuthenticationExtension.cs similarity index 100% rename from Server.Authentication/Extensions/AuthenticationExtension.cs rename to Server/Server.Authentication/Extensions/AuthenticationExtension.cs diff --git a/Server.Authentication/HashingBase64.cs b/Server/Server.Authentication/HashingBase64.cs similarity index 100% rename from Server.Authentication/HashingBase64.cs rename to Server/Server.Authentication/HashingBase64.cs diff --git a/Server.Authentication/Models/AccountOutputModel.cs b/Server/Server.Authentication/Models/AccountOutputModel.cs similarity index 100% rename from Server.Authentication/Models/AccountOutputModel.cs rename to Server/Server.Authentication/Models/AccountOutputModel.cs diff --git a/Server.Authentication/Models/Roles.cs b/Server/Server.Authentication/Models/Roles.cs similarity index 100% rename from Server.Authentication/Models/Roles.cs rename to Server/Server.Authentication/Models/Roles.cs diff --git a/Server.Authentication/Server.Authentication.csproj b/Server/Server.Authentication/Server.Authentication.csproj similarity index 100% rename from Server.Authentication/Server.Authentication.csproj rename to Server/Server.Authentication/Server.Authentication.csproj diff --git a/Server.Authentication/Services/AttemptValidationService.cs b/Server/Server.Authentication/Services/AttemptValidationService.cs similarity index 100% rename from Server.Authentication/Services/AttemptValidationService.cs rename to Server/Server.Authentication/Services/AttemptValidationService.cs diff --git a/Server.Authentication/Services/AuthService.cs b/Server/Server.Authentication/Services/AuthService.cs similarity index 100% rename from Server.Authentication/Services/AuthService.cs rename to Server/Server.Authentication/Services/AuthService.cs diff --git a/Server.Authentication/Services/IAuthService.cs b/Server/Server.Authentication/Services/IAuthService.cs similarity index 100% rename from Server.Authentication/Services/IAuthService.cs rename to Server/Server.Authentication/Services/IAuthService.cs diff --git a/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server/Server.Bll/Exceptions/ExceptionTemplates.cs similarity index 100% rename from Server.Bll/Exceptions/ExceptionTemplates.cs rename to Server/Server.Bll/Exceptions/ExceptionTemplates.cs diff --git a/Server/Server.Bll/Extensions/MappingExtensions.cs b/Server/Server.Bll/Extensions/MappingExtensions.cs new file mode 100644 index 0000000..e8c2e93 --- /dev/null +++ b/Server/Server.Bll/Extensions/MappingExtensions.cs @@ -0,0 +1,14 @@ +// using Mapster; +// using Server.Bll.Models; +// using Server.Data.Entities; +// +// namespace Server.Bll.Extensions; +// +// internal static class MappingExtensions +// { +// internal static readonly TypeAdapterSetter StatisticsAdapterConfig = +// TypeAdapterConfig +// .NewConfig() +// .Map(shortStatisticsModel => shortStatisticsModel.Login, statistics => statistics.Account.Login) +// .Map(shortStatisticsModel => shortStatisticsModel.Score, statistics => statistics.Score); +// } \ No newline at end of file diff --git a/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs similarity index 58% rename from Server.Bll/Extensions/ServiceCollectionExtensions.cs rename to Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs index 851f474..b8a71d6 100644 --- a/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Mapster; +using Microsoft.Extensions.DependencyInjection; using Server.Bll.HostedServices; +using Server.Bll.Models; using Server.Bll.Services; using Server.Bll.Services.Interfaces; +using Server.Data.Entities; namespace Server.Bll.Extensions; @@ -9,6 +13,13 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddBusinessLogic(this IServiceCollection service) { + ArgumentNullException.ThrowIfNull(service); + + TypeAdapterConfig + .NewConfig() + .Map(shortStatisticsModel => shortStatisticsModel.Login, statistics => statistics.Account.Login) + .Map(shortStatisticsModel => shortStatisticsModel.Score, statistics => statistics.Score); + service .AddTransient() .AddTransient() diff --git a/Server.Bll/HostedServices/CleanerHostedService.cs b/Server/Server.Bll/HostedServices/CleanerHostedService.cs similarity index 100% rename from Server.Bll/HostedServices/CleanerHostedService.cs rename to Server/Server.Bll/HostedServices/CleanerHostedService.cs diff --git a/Server.Bll/Models/AccountModel.cs b/Server/Server.Bll/Models/AccountModel.cs similarity index 100% rename from Server.Bll/Models/AccountModel.cs rename to Server/Server.Bll/Models/AccountModel.cs diff --git a/Server.Bll/Models/PlayerModel.cs b/Server/Server.Bll/Models/PlayerModel.cs similarity index 100% rename from Server.Bll/Models/PlayerModel.cs rename to Server/Server.Bll/Models/PlayerModel.cs diff --git a/Server.Bll/Models/RoomModel.cs b/Server/Server.Bll/Models/RoomModel.cs similarity index 100% rename from Server.Bll/Models/RoomModel.cs rename to Server/Server.Bll/Models/RoomModel.cs diff --git a/Server.Bll/Models/RoundModel.cs b/Server/Server.Bll/Models/RoundModel.cs similarity index 100% rename from Server.Bll/Models/RoundModel.cs rename to Server/Server.Bll/Models/RoundModel.cs diff --git a/Server.Bll/Models/ShortStatisticsModel.cs b/Server/Server.Bll/Models/ShortStatisticsModel.cs similarity index 51% rename from Server.Bll/Models/ShortStatisticsModel.cs rename to Server/Server.Bll/Models/ShortStatisticsModel.cs index 34218ed..e349c94 100644 --- a/Server.Bll/Models/ShortStatisticsModel.cs +++ b/Server/Server.Bll/Models/ShortStatisticsModel.cs @@ -1,11 +1,15 @@ +using System.Text.Json.Serialization; + namespace Server.Bll.Models; public sealed class ShortStatisticsModel { - public AccountModel Account { get; set; } + [JsonPropertyName("login")] + public string Login { get; init; } /// /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// - public int? Score { get; set; } + [JsonPropertyName("score")] + public int Score { get; init; } } \ No newline at end of file diff --git a/Server.Bll/Models/StatisticsModel.cs b/Server/Server.Bll/Models/StatisticsModel.cs similarity index 90% rename from Server.Bll/Models/StatisticsModel.cs rename to Server/Server.Bll/Models/StatisticsModel.cs index c36557c..c3b6339 100644 --- a/Server.Bll/Models/StatisticsModel.cs +++ b/Server/Server.Bll/Models/StatisticsModel.cs @@ -2,11 +2,6 @@ public sealed class StatisticsModel { - /// - /// . - /// - public AccountModel Account { get; set; } - /// /// Total amount of Wins /// diff --git a/Server.Bll/Server.Bll.csproj b/Server/Server.Bll/Server.Bll.csproj similarity index 87% rename from Server.Bll/Server.Bll.csproj rename to Server/Server.Bll/Server.Bll.csproj index 5d53c6a..2236c8c 100644 --- a/Server.Bll/Server.Bll.csproj +++ b/Server/Server.Bll/Server.Bll.csproj @@ -15,6 +15,7 @@ + diff --git a/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs similarity index 100% rename from Server.Bll/Services/Interfaces/ILongPollingService.cs rename to Server/Server.Bll/Services/Interfaces/ILongPollingService.cs diff --git a/Server.Bll/Services/Interfaces/IRoomService.cs b/Server/Server.Bll/Services/Interfaces/IRoomService.cs similarity index 95% rename from Server.Bll/Services/Interfaces/IRoomService.cs rename to Server/Server.Bll/Services/Interfaces/IRoomService.cs index 4bf5689..1b48b75 100644 --- a/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using OneOf; -using Server.Bll.Exceptions; +using RockPaperScissors.Common; using Server.Bll.Models; namespace Server.Bll.Services.Interfaces; diff --git a/Server.Bll/Services/Interfaces/IRoundService.cs b/Server/Server.Bll/Services/Interfaces/IRoundService.cs similarity index 92% rename from Server.Bll/Services/Interfaces/IRoundService.cs rename to Server/Server.Bll/Services/Interfaces/IRoundService.cs index 8c655c8..ca8b0ac 100644 --- a/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoundService.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Server.Bll.Models; using OneOf; -using Server.Bll.Exceptions; +using RockPaperScissors.Common; namespace Server.Bll.Services.Interfaces; diff --git a/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs new file mode 100644 index 0000000..500b642 --- /dev/null +++ b/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using OneOf; +using RockPaperScissors.Common; +using Server.Bll.Models; + +namespace Server.Bll.Services.Interfaces; + +public interface IStatisticsService +{ + Task GetAllAsync(); + + Task> GetAsync(string userId); +} \ No newline at end of file diff --git a/Server.Bll/Services/LongPollingService.cs b/Server/Server.Bll/Services/LongPollingService.cs similarity index 100% rename from Server.Bll/Services/LongPollingService.cs rename to Server/Server.Bll/Services/LongPollingService.cs diff --git a/Server.Bll/Services/RoomService.cs b/Server/Server.Bll/Services/RoomService.cs similarity index 99% rename from Server.Bll/Services/RoomService.cs rename to Server/Server.Bll/Services/RoomService.cs index a100e6a..68efe84 100644 --- a/Server.Bll/Services/RoomService.cs +++ b/Server/Server.Bll/Services/RoomService.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using OneOf; +using RockPaperScissors.Common; using Server.Bll.Exceptions; using Server.Bll.Models; using Server.Bll.Services.Interfaces; diff --git a/Server.Bll/Services/RoundService.cs b/Server/Server.Bll/Services/RoundService.cs similarity index 99% rename from Server.Bll/Services/RoundService.cs rename to Server/Server.Bll/Services/RoundService.cs index 2887843..697d00e 100644 --- a/Server.Bll/Services/RoundService.cs +++ b/Server/Server.Bll/Services/RoundService.cs @@ -3,10 +3,11 @@ using System.Threading.Tasks; using Mapster; using Microsoft.EntityFrameworkCore; -using Server.Bll.Exceptions; +using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using OneOf; +using Server.Bll.Exceptions; using Server.Data.Context; using Server.Data.Entities; diff --git a/Server.Bll/Services/StatisticsService.cs b/Server/Server.Bll/Services/StatisticsService.cs similarity index 63% rename from Server.Bll/Services/StatisticsService.cs rename to Server/Server.Bll/Services/StatisticsService.cs index b5b8b13..5d3a6b4 100644 --- a/Server.Bll/Services/StatisticsService.cs +++ b/Server/Server.Bll/Services/StatisticsService.cs @@ -1,12 +1,15 @@ using System; +using System.Linq; using System.Threading.Tasks; using Mapster; using Microsoft.EntityFrameworkCore; -using Server.Bll.Exceptions; +using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; using OneOf; +using Server.Bll.Extensions; using Server.Data.Context; +using Server.Data.Entities; namespace Server.Bll.Services; @@ -19,22 +22,28 @@ public StatisticsService(ServerContext repository) _repository = repository ?? throw new ArgumentNullException(nameof(repository)); } - public Task GetAllStatistics() + public Task GetAllAsync() { return _repository .StatisticsEnumerable + .Include(statistics => statistics.Account) + .OrderByDescending(statistics => statistics.Score) + .Take(10) .ProjectToType() .ToArrayAsync(); } - public async Task> GetPersonalStatistics(string userId) + public async Task> GetAsync(string userId) { var statistics = await _repository.StatisticsEnumerable .Include(stats => stats.Account) .FirstOrDefaultAsync(statistics => statistics.Id.Equals(userId)); - return statistics is not null - ? statistics.Adapt() - : new CustomException($"Unable to get statistics for user \"{userId}\""); + if (statistics is null) + { + return new CustomException($"Unable to get statistics for user \"{userId}\""); + } + + return statistics.Adapt(); } } \ No newline at end of file diff --git a/Server.Data/Context/ServerContext.cs b/Server/Server.Data/Context/ServerContext.cs similarity index 81% rename from Server.Data/Context/ServerContext.cs rename to Server/Server.Data/Context/ServerContext.cs index 5e8ea8f..295eaeb 100644 --- a/Server.Data/Context/ServerContext.cs +++ b/Server/Server.Data/Context/ServerContext.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Server.Data.Entities; +using Server.Data.Extensions; namespace Server.Data.Context; @@ -23,5 +24,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasQueryFilter(round => !round.IsFinished); + + modelBuilder.Entity() + .HasQueryFilter(statistics => statistics.AccountId != SeedingExtension.BotId); } } \ No newline at end of file diff --git a/Server.Data/Entities/Account.cs b/Server/Server.Data/Entities/Account.cs similarity index 100% rename from Server.Data/Entities/Account.cs rename to Server/Server.Data/Entities/Account.cs diff --git a/Server.Data/Entities/Player.cs b/Server/Server.Data/Entities/Player.cs similarity index 100% rename from Server.Data/Entities/Player.cs rename to Server/Server.Data/Entities/Player.cs diff --git a/Server.Data/Entities/Room.cs b/Server/Server.Data/Entities/Room.cs similarity index 100% rename from Server.Data/Entities/Room.cs rename to Server/Server.Data/Entities/Room.cs diff --git a/Server.Data/Entities/Round.cs b/Server/Server.Data/Entities/Round.cs similarity index 100% rename from Server.Data/Entities/Round.cs rename to Server/Server.Data/Entities/Round.cs diff --git a/Server.Data/Entities/Statistics.cs b/Server/Server.Data/Entities/Statistics.cs similarity index 100% rename from Server.Data/Entities/Statistics.cs rename to Server/Server.Data/Entities/Statistics.cs diff --git a/Server.Data/Extensions/DatabaseExtension.cs b/Server/Server.Data/Extensions/DatabaseExtension.cs similarity index 100% rename from Server.Data/Extensions/DatabaseExtension.cs rename to Server/Server.Data/Extensions/DatabaseExtension.cs diff --git a/Server.Data/Extensions/SeedingExtension.cs b/Server/Server.Data/Extensions/SeedingExtension.cs similarity index 100% rename from Server.Data/Extensions/SeedingExtension.cs rename to Server/Server.Data/Extensions/SeedingExtension.cs diff --git a/Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs b/Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs similarity index 99% rename from Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs rename to Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs index e688c16..dfb846b 100644 --- a/Server.Data/Migrations/20221015144504_InitialMigration.Designer.cs +++ b/Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs @@ -11,7 +11,7 @@ namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20221015144504_InitialMigration")] + [Migration("20221015192454_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/Server.Data/Migrations/20221015144504_InitialMigration.cs b/Server/Server.Data/Migrations/20221015192454_InitialMigration.cs similarity index 100% rename from Server.Data/Migrations/20221015144504_InitialMigration.cs rename to Server/Server.Data/Migrations/20221015192454_InitialMigration.cs diff --git a/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server/Server.Data/Migrations/ServerContextModelSnapshot.cs similarity index 100% rename from Server.Data/Migrations/ServerContextModelSnapshot.cs rename to Server/Server.Data/Migrations/ServerContextModelSnapshot.cs diff --git a/Server.Data/Server.Data.csproj b/Server/Server.Data/Server.Data.csproj similarity index 100% rename from Server.Data/Server.Data.csproj rename to Server/Server.Data/Server.Data.csproj diff --git a/Server.Host/Controllers/AccountController.cs b/Server/Server.Host/Controllers/AccountController.cs similarity index 85% rename from Server.Host/Controllers/AccountController.cs rename to Server/Server.Host/Controllers/AccountController.cs index 502faa2..ddb9a1c 100644 --- a/Server.Host/Controllers/AccountController.cs +++ b/Server/Server.Host/Controllers/AccountController.cs @@ -2,12 +2,14 @@ using System.Net; using System.Net.Mime; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using RockPaperScissors.Common.Models; +using RockPaperScissors.Common.Requests; using Server.Authentication.Exceptions; +using Server.Authentication.Models; using Server.Authentication.Services; -using Server.Host.Contracts; -using Server.Host.Contracts.Requests; namespace Server.Host.Controllers; @@ -38,7 +40,7 @@ public async Task RegisterAsync(RegisterRequest registerRequest) } [HttpPost("login")] - [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(AccountOutputModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] public async Task LoginAsync(AccountDto accountDto) { @@ -46,10 +48,11 @@ public async Task LoginAsync(AccountDto accountDto) await _authService.LoginAsync(accountDto.Login, accountDto.Password); return newAccount.Match( - statusCode => Ok(statusCode), + accountOutputModel => Ok(accountOutputModel), userException => BadRequest(userException)); } + [Authorize] [HttpGet("logout")] [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] diff --git a/Server.Host/Controllers/LongPollingController.cs b/Server/Server.Host/Controllers/LongPollingController.cs similarity index 100% rename from Server.Host/Controllers/LongPollingController.cs rename to Server/Server.Host/Controllers/LongPollingController.cs diff --git a/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs similarity index 100% rename from Server.Host/Controllers/RoomController.cs rename to Server/Server.Host/Controllers/RoomController.cs diff --git a/Server.Host/Controllers/RoundController.cs b/Server/Server.Host/Controllers/RoundController.cs similarity index 100% rename from Server.Host/Controllers/RoundController.cs rename to Server/Server.Host/Controllers/RoundController.cs diff --git a/Server.Host/Controllers/StatisticsController.cs b/Server/Server.Host/Controllers/StatisticsController.cs similarity index 90% rename from Server.Host/Controllers/StatisticsController.cs rename to Server/Server.Host/Controllers/StatisticsController.cs index b86d5b9..65a124e 100644 --- a/Server.Host/Controllers/StatisticsController.cs +++ b/Server/Server.Host/Controllers/StatisticsController.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Server.Bll.Exceptions; +using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -33,7 +33,7 @@ public StatisticsController(IStatisticsService statisticsService) [ProducesResponseType(StatusCodes.Status400BadRequest)] public Task GetOverallStatistics() { - return _statisticsService.GetAllStatistics(); + return _statisticsService.GetAllAsync(); } [Authorize] @@ -42,7 +42,7 @@ public Task GetOverallStatistics() [ProducesResponseType(typeof(CustomException), StatusCodes.Status400BadRequest)] public async Task GetPersonalStatistics() { - var result = await _statisticsService.GetPersonalStatistics(UserId); + var result = await _statisticsService.GetAsync(UserId); return result.Match( statsModel => Ok(statsModel), diff --git a/Server.Host/Extensions/LoggingMiddleware.cs b/Server/Server.Host/Extensions/LoggingMiddleware.cs similarity index 100% rename from Server.Host/Extensions/LoggingMiddleware.cs rename to Server/Server.Host/Extensions/LoggingMiddleware.cs diff --git a/Server.Host/Extensions/SwaggerExtension.cs b/Server/Server.Host/Extensions/SwaggerExtension.cs similarity index 100% rename from Server.Host/Extensions/SwaggerExtension.cs rename to Server/Server.Host/Extensions/SwaggerExtension.cs diff --git a/Server.Host/Program.cs b/Server/Server.Host/Program.cs similarity index 100% rename from Server.Host/Program.cs rename to Server/Server.Host/Program.cs diff --git a/Server.Host/Properties/launchSettings.json b/Server/Server.Host/Properties/launchSettings.json similarity index 100% rename from Server.Host/Properties/launchSettings.json rename to Server/Server.Host/Properties/launchSettings.json diff --git a/Server.Host/Server.Host.csproj b/Server/Server.Host/Server.Host.csproj similarity index 91% rename from Server.Host/Server.Host.csproj rename to Server/Server.Host/Server.Host.csproj index 5f16570..632ac61 100644 --- a/Server.Host/Server.Host.csproj +++ b/Server/Server.Host/Server.Host.csproj @@ -23,10 +23,9 @@ + - - diff --git a/Server.Host/Startup.cs b/Server/Server.Host/Startup.cs similarity index 100% rename from Server.Host/Startup.cs rename to Server/Server.Host/Startup.cs diff --git a/Server.Host/appsettings.json b/Server/Server.Host/appsettings.json similarity index 100% rename from Server.Host/appsettings.json rename to Server/Server.Host/appsettings.json From e95f88665623175c8d1112543a91331a2e6a79be Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 16 Oct 2022 00:11:55 +0300 Subject: [PATCH 48/56] removed global.json --- global.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 global.json diff --git a/global.json b/global.json deleted file mode 100644 index 87aef9f..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "6.0.0", - "rollForward": "latestMajor", - "allowPrerelease": false - } -} \ No newline at end of file From 49348c30e24fe9cc9579e72898c73665bcea27a1 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 16 Oct 2022 02:09:40 +0300 Subject: [PATCH 49/56] Added healthchecks --- .../Client.Host/Extensions/JsonExtension.cs | 38 ------ Client/Client.Host/Menus/AccountMenu.cs | 101 --------------- Client/Client.Host/Menus/IAccountMenu.cs | 13 -- Client/Client.Host/Menus/StartMenu.cs | 121 ------------------ Client/Client.Host/Models/Account.cs | 13 -- Client/Client.Host/Models/ErrorModel.cs | 11 -- .../Client.Host/Models/Interfaces/IAccount.cs | 8 -- .../Models/Interfaces/IOverallStatistics.cs | 8 -- Client/Client.Host/Models/Interfaces/IRoom.cs | 21 --- .../Client.Host/Models/Interfaces/IRound.cs | 26 ---- .../Models/Interfaces/IStatistics.cs | 55 -------- .../Models/Interfaces/StatisticsDto.cs | 17 --- Client/Client.Host/Models/Room.cs | 23 ---- Client/Client.Host/Models/Round.cs | 30 ----- Client/Client.Host/Models/Statistics.cs | 75 ----------- Client/Client.Host/Models/TokenModel.cs | 12 -- Client/Client.Host/Program.cs | 38 ++---- .../RequestProcessor/IRequestHandler.cs | 9 -- .../RequestProcessor/IRequestPerformer.cs | 9 -- .../RequestProcessor/Impl/RequestHandler.cs | 77 ----------- .../RequestProcessor/Impl/RequestPerformer.cs | 28 ---- .../RequestModels/IRequestOptions.cs | 20 --- .../RequestModels/IResponse.cs | 8 -- .../RequestModels/Impl/RequestOptions.cs | 23 ---- .../RequestModels/Impl/Response.cs | 16 --- .../RequestModels/RequestMethod.cs | 11 -- .../Extensions/ServiceCollectionExtensions.cs | 21 ++- Client/Client.StartMenu/Menus/StartMenu.cs | 22 +++- .../Services/HealthCheckService.cs | 51 ++++++++ .../Services/IHealthCheckService.cs | 8 ++ .../Client.Statistics/Menus/StatisticsMenu.cs | 8 +- .../RockPaperScissors.Common/Client/Client.cs | 25 ++++ .../Client/IClient.cs | 4 + .../Extensions/ServiceCollectionExtensions.cs | 1 - .../CleanerHostedService.cs | 2 +- Server/Server.Host/Startup.cs | 5 + 36 files changed, 145 insertions(+), 813 deletions(-) delete mode 100644 Client/Client.Host/Extensions/JsonExtension.cs delete mode 100644 Client/Client.Host/Menus/AccountMenu.cs delete mode 100644 Client/Client.Host/Menus/IAccountMenu.cs delete mode 100644 Client/Client.Host/Menus/StartMenu.cs delete mode 100644 Client/Client.Host/Models/Account.cs delete mode 100644 Client/Client.Host/Models/ErrorModel.cs delete mode 100644 Client/Client.Host/Models/Interfaces/IAccount.cs delete mode 100644 Client/Client.Host/Models/Interfaces/IOverallStatistics.cs delete mode 100644 Client/Client.Host/Models/Interfaces/IRoom.cs delete mode 100644 Client/Client.Host/Models/Interfaces/IRound.cs delete mode 100644 Client/Client.Host/Models/Interfaces/IStatistics.cs delete mode 100644 Client/Client.Host/Models/Interfaces/StatisticsDto.cs delete mode 100644 Client/Client.Host/Models/Room.cs delete mode 100644 Client/Client.Host/Models/Round.cs delete mode 100644 Client/Client.Host/Models/Statistics.cs delete mode 100644 Client/Client.Host/Models/TokenModel.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs delete mode 100644 Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs create mode 100644 Client/Client.StartMenu/Services/HealthCheckService.cs create mode 100644 Client/Client.StartMenu/Services/IHealthCheckService.cs rename Server/Server.Bll/{HostedServices => Services}/CleanerHostedService.cs (97%) diff --git a/Client/Client.Host/Extensions/JsonExtension.cs b/Client/Client.Host/Extensions/JsonExtension.cs deleted file mode 100644 index f229a49..0000000 --- a/Client/Client.Host/Extensions/JsonExtension.cs +++ /dev/null @@ -1,38 +0,0 @@ -// using Client.Host.Services.RequestProcessor.RequestModels; -// using Newtonsoft.Json; -// using Newtonsoft.Json.Linq; -// using Newtonsoft.Json.Schema; -// -// namespace Client.Host.Extensions; -// -// public static class JsonExtension -// { -// private const string Schema = @"{ -// 'code': {'type': 'integer'}, -// 'message': {'type': 'string'}, -// }"; -// -// private static JSchema JSchema => JSchema.Parse(Schema); -// -// public static bool TryParseJson(this IResponse json, out T deserialized) where T : new() -// { -// if (string.IsNullOrEmpty(json.Content) || int.TryParse(json.Content, out _)) -// { -// deserialized = default; -// return false; -// } -// -// var jObject = JObject.Parse(json.Content); -// -// var isValid = jObject.IsValid(JSchema); -// -// if (!isValid) -// { -// deserialized = default; -// return false; -// } -// -// deserialized = JsonConvert.DeserializeObject(json.Content); -// return true; -// } -// } \ No newline at end of file diff --git a/Client/Client.Host/Menus/AccountMenu.cs b/Client/Client.Host/Menus/AccountMenu.cs deleted file mode 100644 index 77ca133..0000000 --- a/Client/Client.Host/Menus/AccountMenu.cs +++ /dev/null @@ -1,101 +0,0 @@ -// using System; -// using System.Collections.Generic; -// using System.Threading.Tasks; -// using Client.Host.Extensions; -// using Client.Host.Models; -// using Client.Host.Services; -// using Client.Host.Services.RequestProcessor; -// using Client.Host.Services.RequestProcessor.RequestModels; -// using Client.Host.Services.RequestProcessor.RequestModels.Impl; -// using Newtonsoft.Json; -// -// namespace Client.Host.Menus; -// -// public class AccountMenu : IAccountMenu -// { -// private readonly IRequestPerformer _performer; -// -// public AccountMenu(IRequestPerformer performer) -// { -// _performer = performer; -// } -// -// public async Task RegisterAsync() -// { -// TextWrite.Print("\nWe are glad to welcome you in the registration form!\n" + -// "Please enter the required details\n" + -// "to register an account on the platform", ConsoleColor.Magenta); -// var registrationAccount = new Account -// { -// Login = new StringPlaceholder().BuildString("Login"), -// Password = -// new StringPlaceholder(StringDestination.Password).BuildString("Password") -// }; -// -// var options = new RequestOptions -// { -// ContentType = "application/json", -// Body = JsonConvert.SerializeObject(registrationAccount), -// Address = "account/register", -// IsValid = true, -// Method = RequestMethod.Post, -// Name = "Registration" -// }; -// var reachedResponse = await _performer.PerformRequestAsync(options); -// -// if (reachedResponse.TryParseJson(out var errorModel)) -// { -// TextWrite.Print(errorModel.Message, ConsoleColor.Red); -// return false; -// } -// -// TextWrite.Print("Successfully registered!", ConsoleColor.Green); -// return true; -// } -// -// public async Task<(string token, TokenModel inputAccount)> LoginAsync() -// { -// var inputAccount = new Account -// { -// Login = new StringPlaceholder().BuildString("Login"), -// Password = -// new StringPlaceholder(StringDestination.Password).BuildString("Password", true) -// }; -// var options = new RequestOptions -// { -// ContentType = "application/json", -// Body = JsonConvert.SerializeObject(inputAccount), -// Address = "account/login", -// IsValid = true, -// Method = RequestMethod.Post, -// Name = "Login" -// }; -// var reachedResponse = await _performer.PerformRequestAsync(options); -// -// if (reachedResponse.TryParseJson(out var tokenModel)) -// { -// TextWrite.Print($"Successfully signed in.", ConsoleColor.DarkGreen); -// return (tokenModel.Token, tokenModel); -// } -// -// var error = JsonConvert.DeserializeObject(reachedResponse.Content); -// if(error is null) return (null, null); -// -// TextWrite.Print(error.Message, ConsoleColor.Red); -// return (null, null); -// } -// -// public async Task LogoutAsync(string token) -// { -// var options = new RequestOptions -// { -// Headers = new Dictionary{{"Authorization",token}}, -// Address = $"user/logout/{token}", -// IsValid = true, -// Method = RequestMethod.Get -// }; -// await _performer.PerformRequestAsync(options); -// TextWrite.Print("Successfully signed out", ConsoleColor.Green); -// return true; -// } -// } \ No newline at end of file diff --git a/Client/Client.Host/Menus/IAccountMenu.cs b/Client/Client.Host/Menus/IAccountMenu.cs deleted file mode 100644 index 2c199f5..0000000 --- a/Client/Client.Host/Menus/IAccountMenu.cs +++ /dev/null @@ -1,13 +0,0 @@ -// using System.Threading.Tasks; -// using Client.Host.Models; -// -// namespace Client.Host.Menus; -// -// public interface IAccountMenu -// { -// Task RegisterAsync(); -// -// Task<(string token, TokenModel inputAccount)> LoginAsync(); -// -// Task LogoutAsync(string token); -// } \ No newline at end of file diff --git a/Client/Client.Host/Menus/StartMenu.cs b/Client/Client.Host/Menus/StartMenu.cs deleted file mode 100644 index 474e6a8..0000000 --- a/Client/Client.Host/Menus/StartMenu.cs +++ /dev/null @@ -1,121 +0,0 @@ -// using System; -// using System.Threading; -// using System.Threading.Tasks; -// using Client.Host.Models; -// using Client.Host.Services; -// using Client.Host.Services.RequestProcessor; -// -// namespace Client.Host.Menus; -// -// public class StartMenu -// { -// private readonly IAccountMenu _accountMenu; -// private readonly IStatisticsService _statisticsService; -// private readonly IRequestPerformer _requestPerformer; -// -// private string SessionId { get; set; } -// -// public StartMenu(IRequestPerformer performer) -// { -// _requestPerformer = performer; -// _statisticsService = new StatisticsService(performer); -// _accountMenu = new AccountMenu(performer); -// } -// -// public async Task StartAsync() -// { -// var tokenSource = new CancellationTokenSource(); -// var token = tokenSource.Token; -// -// await Greeting().ConfigureAwait(false); -// TextWrite.Print("\n\nPress any key to show start up menu list.", ConsoleColor.Green); -// -// Console.ReadKey(); -// Console.Clear(); -// //todo: trying to connect to the server -// await Menu(token); -// return 1; -// } -// private async Task Menu(CancellationToken token) -// { -// while (true) -// { -// TextWrite.Print("Start menu:\n" + -// "1.\tSign up\n" + -// "2.\tLog in\n" + -// "3.\tSee Leaderboard\n" + //This part will be available after we figure out the statistics -// "4.\tExit", ConsoleColor.DarkYellow); -// -// TextWrite.Print("\nPlease select an item from the list", ConsoleColor.Green); -// -// Console.Write("Select -> "); -// if (token.IsCancellationRequested) -// { -// return; -// } -// var passed = int.TryParse(Console.ReadLine(), out var startMenuInput); -// if (!passed) -// { -// TextWrite.Print("Invalid input. Try again.", ConsoleColor.Red); -// continue; -// } -// switch (startMenuInput) -// { -// case 1: -// await _accountMenu.RegisterAsync(); -// TextWrite.Print( -// "\n\nPress any key to back to the start up menu list!", ConsoleColor.Cyan); -// Console.ReadKey(); -// Console.Clear(); -// break; -// case 2: -// TokenModel inputAccount; -// (SessionId, inputAccount) = await _accountMenu.LoginAsync(); -// if (!string.IsNullOrEmpty(SessionId)) -// { -// Console.Clear(); -// await new MainMenu(inputAccount,_requestPerformer,_statisticsService) -// .PlayerMenu(); -// } -// Console.Clear(); -// break; -// case 3: -// var result = await _statisticsService.GetAllStatistics(); -// await _statisticsService.PrintStatistics(result); -// /*var statistics = await OverallStatistics(); -// if(statistics == null) -// Console.WriteLine("No statistics so far"); -// else -// { -// PrintStatistics(statistics); -// }*/ -// break; -// case 4: -// if (await _accountMenu.LogoutAsync(SessionId)) -// { -// Console.WriteLine("DEBUG: Logged out"); -// return; -// } -// else -// { -// throw new NotImplementedException(); -// } -// default: -// TextWrite.Print("Unsupported input", ConsoleColor.Red); -// continue; -// } -// } -// } -// -// private static Task Greeting() -// { -// TextWrite.Print( -// "VERSION 2.0\n" + -// "Welcome to the world best game ----> Rock-Paper-Scissors!\n" + -// "You are given the opportunity to compete with other users in this wonderful game,\n" + -// "or if you don’t have anyone to play, don’t worry,\n" + -// "you can find a random player or just try your skill with a bot.", ConsoleColor.White); -// TextWrite.Print("(c)Ihor Volokhovych & Michael Terekhov", ConsoleColor.Cyan); -// return Task.CompletedTask; -// } -// } \ No newline at end of file diff --git a/Client/Client.Host/Models/Account.cs b/Client/Client.Host/Models/Account.cs deleted file mode 100644 index cf6d805..0000000 --- a/Client/Client.Host/Models/Account.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; -using Client.Host.Models.Interfaces; - -namespace Client.Host.Models; - -public class Account : IAccount -{ - [JsonPropertyName("Login")] - public string Login { get; set; } - - [JsonPropertyName("Password")] - public string Password { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/ErrorModel.cs b/Client/Client.Host/Models/ErrorModel.cs deleted file mode 100644 index 86fa019..0000000 --- a/Client/Client.Host/Models/ErrorModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Client.Host.Models; - -public class ErrorModel -{ - [JsonPropertyName("Code")] - public int Code { get; set; } - [JsonPropertyName("Message")] - public string Message { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/IAccount.cs b/Client/Client.Host/Models/Interfaces/IAccount.cs deleted file mode 100644 index 31fd162..0000000 --- a/Client/Client.Host/Models/Interfaces/IAccount.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Client.Host.Models.Interfaces; - -public interface IAccount -{ - public string Login { get; set; } - - public string Password { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/IOverallStatistics.cs b/Client/Client.Host/Models/Interfaces/IOverallStatistics.cs deleted file mode 100644 index 749a66f..0000000 --- a/Client/Client.Host/Models/Interfaces/IOverallStatistics.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Client.Host.Models.Interfaces; - -public interface IOverallStatistics -{ - Account Account { get; set; } - - int Score { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/IRoom.cs b/Client/Client.Host/Models/Interfaces/IRoom.cs deleted file mode 100644 index e9af15e..0000000 --- a/Client/Client.Host/Models/Interfaces/IRoom.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Text.Json.Serialization; - -namespace Client.Host.Models.Interfaces; - -internal interface IRoom -{ - [JsonPropertyName("RoomId")] - string RoomId { get; set; } - - [JsonPropertyName("Players")] - ConcurrentDictionary Players { get; set; } - - [JsonPropertyName("CurrentRoundId")] - string CurrentRoundId { get; set; } - - [JsonPropertyName("CreationTime")] - DateTime CreationTime { get; set; } - bool IsReady { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/IRound.cs b/Client/Client.Host/Models/Interfaces/IRound.cs deleted file mode 100644 index cc013ba..0000000 --- a/Client/Client.Host/Models/Interfaces/IRound.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Text.Json.Serialization; - -namespace Client.Host.Models.Interfaces; - -internal interface IRound -{ - [JsonPropertyName("Id")] - string Id { get; init; } //Not to store identical rounds - - [JsonPropertyName("Moves")] - ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - - [JsonPropertyName("IsFinished")] - bool IsFinished { get; set; } //Probably not needed. - - [JsonPropertyName("TimeFinished")] - DateTime TimeFinished { get; set; } - - [JsonPropertyName("WinnerId")] - string WinnerId { get; set; } - - [JsonPropertyName("LoserId")] - string LoserId { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/IStatistics.cs b/Client/Client.Host/Models/Interfaces/IStatistics.cs deleted file mode 100644 index 8886343..0000000 --- a/Client/Client.Host/Models/Interfaces/IStatistics.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Client.Host.Models.Interfaces; - -public interface IStatistics -{ - /// - /// This user account - /// - Account Account { get; set; } - /// - /// Total amount of Wins - /// - int Wins { get; set; } - - /// - /// Total amount of Loses - /// - int Loss { get; set; } - - /// - /// Total amount of Draws. OBSOLETE - /// - - int Draws { get; set; } - - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - double WinLossRatio { get; set; } - - /// - /// Ratio for the last 7 days - /// - string TimeSpent { get; set; } - - /// - /// Times used rock - /// - int UsedRock { get; set; } - - /// - /// Times used Paper - /// - int UsedPaper { get; set; } - - /// - /// Times used Scissors - /// - int UsedScissors { get; set; } - - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - int Score { get; set; } - -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Interfaces/StatisticsDto.cs b/Client/Client.Host/Models/Interfaces/StatisticsDto.cs deleted file mode 100644 index 10418cd..0000000 --- a/Client/Client.Host/Models/Interfaces/StatisticsDto.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Client.Host.Models.Interfaces; - -public class StatisticsDto -{ - [JsonPropertyName("Login")] - public string Login { get; set; } - - [JsonPropertyName("Score")] - public int Score { get; set; } - - public override string ToString() - { - return $"Login: {Login} ; Score: {Score}\n"; - } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Room.cs b/Client/Client.Host/Models/Room.cs deleted file mode 100644 index c06f172..0000000 --- a/Client/Client.Host/Models/Room.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Text.Json.Serialization; -using Client.Host.Models.Interfaces; - -namespace Client.Host.Models; - -public class Room : IRoom -{ - [JsonPropertyName("RoomId")] - public string RoomId { get; set; } - - [JsonPropertyName("Players")] - public ConcurrentDictionary Players { get; set; } - - [JsonPropertyName("CurrentRoundId")] - public string CurrentRoundId { get; set; } - - [JsonPropertyName("CreationTime")] - public DateTime CreationTime { get; set; } - - public bool IsReady { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Round.cs b/Client/Client.Host/Models/Round.cs deleted file mode 100644 index dfa12c5..0000000 --- a/Client/Client.Host/Models/Round.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Text.Json.Serialization; -using Client.Host.Models.Interfaces; - -namespace Client.Host.Models; - -internal class Round : IRound -{ - [JsonPropertyName("Id")] - public string Id { get; init; } //Not to store identical rounds - - [JsonPropertyName("Moves")] - public ConcurrentDictionary PlayerMoves { get; set; } //where string key is playerId - - [JsonPropertyName("IsFinished")] - public bool IsFinished { get; set; } //Probably not needed. - - [JsonPropertyName("TimeFinished")] - public DateTime TimeFinished { get; set; } - - [JsonPropertyName("WinnerId")] - public string WinnerId { get; set; } - - [JsonPropertyName("LoserId")] - public string LoserId { get; set; } - - [JsonPropertyName("IsDraw")] - public bool IsDraw { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/Statistics.cs b/Client/Client.Host/Models/Statistics.cs deleted file mode 100644 index 5eb1cbf..0000000 --- a/Client/Client.Host/Models/Statistics.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Client.Host.Models.Interfaces; - -namespace Client.Host.Models; - -public class Statistics : IStatistics, IOverallStatistics -{ - - /// - /// user - /// - public Account Account { get; set; } - /// - /// Total amount of Wins - /// - public int Wins { get; set; } - - /// - /// Total amount of Loses - /// - public int Loss { get; set; } - - /// - /// Total amount of Draws. OBSOLETE - /// - - public int Draws { get; set; } - - /// - /// Ratio Wins to Losses. Win/Loss * 100 - /// - public double WinLossRatio { get; set; } - - /// - /// Ratio for the last 7 days - /// - public string TimeSpent { get; set; } - - /// - /// Times used rock - /// - public int UsedRock { get; set; } - - /// - /// Times used Paper - /// - public int UsedPaper { get; set; } - - /// - /// Times used Scissors - /// - public int UsedScissors { get; set; } - - /// - /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. - /// - public int Score { get; set; } - - public override string ToString() - { - return $"Times Won: {Wins}\n" + - $"Times Lost:{Loss}\n" + - $"Win to Loss ratio: {WinLossRatio}\n" + - $"{TimeSpent}\n" + - $"Times used rock: {UsedRock}\n" + - $"Times used paper: {UsedPaper}\n" + - $"Times used scissors: {UsedScissors}\n" + - $"Total score: {Score}"; - } - - public string ToShortString() - { - return $"UserName: {Account.Login}" + - $"Score: {Score}"; - } -} \ No newline at end of file diff --git a/Client/Client.Host/Models/TokenModel.cs b/Client/Client.Host/Models/TokenModel.cs deleted file mode 100644 index 51e83f9..0000000 --- a/Client/Client.Host/Models/TokenModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Client.Host.Models; - -public class TokenModel -{ - [JsonIgnore] public string BearerToken => "Bearer " + Token; - [JsonPropertyName("Token")] - public string Token {get; set; } - [JsonPropertyName("Login")] - public string Login { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Program.cs b/Client/Client.Host/Program.cs index 083e112..0ddc2a5 100644 --- a/Client/Client.Host/Program.cs +++ b/Client/Client.Host/Program.cs @@ -9,43 +9,31 @@ namespace Client.Host; internal static class Program { - // private static async Task MainV1() - // { - // try - // { - // var client = new HttpClient {BaseAddress = new Uri("http://localhost:5000/api/v1/")}; - // var clientHandler = new HttpClientHandler(); - // var requestHandler = new RequestHandler(client, clientHandler); - // var requestPerformer = new RequestPerformer(requestHandler); - // - // var startMenu = new StartMenu(requestPerformer); - // return await startMenu.StartAsync(); - // } - // catch (Exception) //todo : do this need a message? - // { - // Console.WriteLine("Unknown error occured. Crash."); - // return -1; - // } - // } - private static async Task Main() { - var cancellationToken = new CancellationTokenSource(); + var cancellationTokenSource = new CancellationTokenSource(); + try { - var client = RockPaperScissors.Common.Client.Client.Create("http://localhost:5000"); + var baseAddress = "http://localhost:5000"; + var client = RockPaperScissors.Common.Client.Client.Create(baseAddress); var accountService = client.CreateAccountService(); var statisticsService = client.CreateStatisticsService(); + var healthCheckService = client.CreateHealthCheckService(cancellationTokenSource); var accountMenu = accountService.CreateAccountMenu(); var statisticsMenu = statisticsService.CreateStatisticsMenu(accountService); - var startMenu = statisticsMenu.CreateStartMenu(accountMenu); + var startMenu = statisticsMenu.CreateStartMenu(accountMenu, healthCheckService); + + await startMenu.PrintAsync(cancellationTokenSource.Token); - await startMenu.PrintAsync(cancellationToken.Token); - return 0; } - catch (Exception exception) //todo : do we need a message? + catch (TaskCanceledException) + { + return -1; + } + catch (Exception exception) { Console.WriteLine(exception.Message); Console.WriteLine("Unknown error occured. Closing."); diff --git a/Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs b/Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs deleted file mode 100644 index 4ea855c..0000000 --- a/Client/Client.Host/Services/RequestProcessor/IRequestHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; -using Client.Host.Services.RequestProcessor.RequestModels; - -namespace Client.Host.Services.RequestProcessor; - -public interface IRequestHandler -{ - Task HandleRequestAsync(IRequestOptions requestOptions); -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs b/Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs deleted file mode 100644 index 0793cf0..0000000 --- a/Client/Client.Host/Services/RequestProcessor/IRequestPerformer.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; -using Client.Host.Services.RequestProcessor.RequestModels; - -namespace Client.Host.Services.RequestProcessor; - -public interface IRequestPerformer -{ - Task PerformRequestAsync(IRequestOptions requestOptions); -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs b/Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs deleted file mode 100644 index 0db90d9..0000000 --- a/Client/Client.Host/Services/RequestProcessor/Impl/RequestHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Client.Host.Services.RequestProcessor.RequestModels; -using Client.Host.Services.RequestProcessor.RequestModels.Impl; - -namespace Client.Host.Services.RequestProcessor.Impl; - -public class RequestHandler : IRequestHandler -{ - private readonly HttpClient _client; - private readonly HttpClientHandler _httpClientHandler; - - public RequestHandler(HttpClient httpClient, HttpClientHandler httpClientHandler) - { - _client = httpClient; - _httpClientHandler = httpClientHandler; - } - - public async Task HandleRequestAsync(IRequestOptions requestOptions) - { - //var handler = new HttpClientHandler(); - _httpClientHandler - .ServerCertificateCustomValidationCallback - += (_, _, _, _) => true; - if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); - if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - - using var msg = - new HttpRequestMessage(MapMethod(requestOptions.Method), - new Uri(_client.BaseAddress+requestOptions.Address)); - try - { - if(requestOptions.Headers != null) - foreach (var (key, value) in requestOptions.Headers) - { - msg.Headers.Add(key,value); - } - - if (MapMethod(requestOptions.Method) == HttpMethod.Delete) - { - using var responseD = await _client.SendAsync(msg); - var bodyD = await responseD.Content.ReadAsStringAsync(); - return new Response(true, (int)responseD.StatusCode, bodyD); - } - - if (MapMethod(requestOptions.Method) != HttpMethod.Get) - { - msg.Content = new StringContent(requestOptions.Body, Encoding.UTF8, requestOptions.ContentType); - using var responseForPushingData = await _client.SendAsync(msg); - var bodyForPushing = await responseForPushingData.Content.ReadAsStringAsync(); - return new Response(true, (int)responseForPushingData.StatusCode, bodyForPushing); - } - - using var response = await _client.SendAsync(msg); - var body = await response.Content.ReadAsStringAsync(); - return new Response(true, (int)response.StatusCode, body); - } - catch (HttpRequestException) //todo: probably redo - { - return new Response(false, 500, "Server.Host is not responding!"); - } - } - private static HttpMethod MapMethod(RequestMethod method) - { - return method switch - { - RequestMethod.Get => HttpMethod.Get, - RequestMethod.Post => HttpMethod.Post, - RequestMethod.Put => HttpMethod.Put, - RequestMethod.Patch => HttpMethod.Patch, - RequestMethod.Delete => HttpMethod.Delete, - _ => throw new ArgumentOutOfRangeException(nameof(method), method, "Invalid request method") - }; - } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs b/Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs deleted file mode 100644 index ff006d1..0000000 --- a/Client/Client.Host/Services/RequestProcessor/Impl/RequestPerformer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading.Tasks; -using Client.Host.Services.RequestProcessor.RequestModels; - -namespace Client.Host.Services.RequestProcessor.Impl; - -public class RequestPerformer : IRequestPerformer -{ - private readonly IRequestHandler _requestHandler; - public RequestPerformer(IRequestHandler requestHandler) - { - _requestHandler = requestHandler; - } - public async Task PerformRequestAsync(IRequestOptions requestOptions) - { - if (requestOptions == null) throw new ArgumentNullException(nameof(requestOptions)); - if (!requestOptions.IsValid) throw new ArgumentOutOfRangeException(nameof(requestOptions)); - try - { - return await _requestHandler.HandleRequestAsync(requestOptions); - } - catch (TimeoutException) //todo: Probably redo - { - //response = new Response(false, 408, null); - return null; - } - } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs deleted file mode 100644 index d8ac9b7..0000000 --- a/Client/Client.Host/Services/RequestProcessor/RequestModels/IRequestOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; - -namespace Client.Host.Services.RequestProcessor.RequestModels; - -public interface IRequestOptions -{ - Dictionary Headers { get; } - - string Name { get; } - - string Address { get; } - - RequestMethod Method { get; } - - string ContentType { get; } - - string Body { get; } - - bool IsValid { get; } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs deleted file mode 100644 index 0a6d2ab..0000000 --- a/Client/Client.Host/Services/RequestProcessor/RequestModels/IResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Client.Host.Services.RequestProcessor.RequestModels; - -public interface IResponse -{ - public bool Handled { get; } - public int Code { get; } - public string Content { get; } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs deleted file mode 100644 index 2e5c2ff..0000000 --- a/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/RequestOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Client.Host.Services.RequestProcessor.RequestModels.Impl; - -public class RequestOptions : IRequestOptions -{ - public Dictionary Headers { get; set; } - public string Name { get; set; } - - public string Address { get; set; } - - public RequestMethod Method { get; set; } - - public string ContentType { get; set; } - - public string Body { get; set; } - - public bool IsValid { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs deleted file mode 100644 index 6ce3d44..0000000 --- a/Client/Client.Host/Services/RequestProcessor/RequestModels/Impl/Response.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Client.Host.Services.RequestProcessor.RequestModels.Impl; - -public class Response : IResponse -{ - public Response(bool handled, int code, string content) - { - Handled = handled; - Code = code; - Content = content; - } - public bool Handled { get; set; } - - public int Code { get; set; } - - public string Content { get; set; } -} \ No newline at end of file diff --git a/Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs b/Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs deleted file mode 100644 index fae404e..0000000 --- a/Client/Client.Host/Services/RequestProcessor/RequestModels/RequestMethod.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Client.Host.Services.RequestProcessor.RequestModels; - -public enum RequestMethod -{ - Undefined = 0, - Get = 1, - Post = 2, - Put = 3, - Patch = 4, - Delete =5 -} \ No newline at end of file diff --git a/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs b/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs index 3e7e90c..c8f39c7 100644 --- a/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs +++ b/Client/Client.StartMenu/Extensions/ServiceCollectionExtensions.cs @@ -1,16 +1,31 @@ using Client.Account.Menus; using Client.StartMenu.Menus; +using Client.StartMenu.Services; using Client.Statistics.Menus; +using RockPaperScissors.Common.Client; namespace Client.StartMenu.Extensions; public static class ServiceCollectionExtensions { - public static IStartMenu CreateStartMenu(this IStatisticsMenu statisticsMenu, IAccountMenu accountMenu) + public static IStartMenu CreateStartMenu( + this IStatisticsMenu statisticsMenu, + IAccountMenu accountMenu, + IHealthCheckService healthCheckService) { ArgumentNullException.ThrowIfNull(accountMenu); ArgumentNullException.ThrowIfNull(statisticsMenu); - - return new Menus.StartMenu(accountMenu, statisticsMenu); + + return new Menus.StartMenu(accountMenu, statisticsMenu, healthCheckService); + } + + public static IHealthCheckService CreateHealthCheckService( + this IClient client, + CancellationTokenSource cancellationTokenSource) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(cancellationTokenSource); + + return new HealthCheckService(client, cancellationTokenSource); } } \ No newline at end of file diff --git a/Client/Client.StartMenu/Menus/StartMenu.cs b/Client/Client.StartMenu/Menus/StartMenu.cs index c305752..82d4ea0 100644 --- a/Client/Client.StartMenu/Menus/StartMenu.cs +++ b/Client/Client.StartMenu/Menus/StartMenu.cs @@ -1,6 +1,7 @@ using Client.Account.Menus; using Client.StartMenu.Enums; using Client.StartMenu.Extensions; +using Client.StartMenu.Services; using Client.Statistics.Menus; using RockPaperScissors.Common.Extensions; @@ -10,26 +11,35 @@ internal sealed class StartMenu: IStartMenu { private readonly IAccountMenu _accountMenu; private readonly IStatisticsMenu _statisticsMenu; + private readonly IHealthCheckService _healthCheckService; - public StartMenu(IAccountMenu accountMenu, IStatisticsMenu statisticsMenu) + public StartMenu( + IAccountMenu accountMenu, + IStatisticsMenu statisticsMenu, + IHealthCheckService healthCheckService) { _accountMenu = accountMenu ?? throw new ArgumentNullException(nameof(accountMenu)); _statisticsMenu = statisticsMenu ?? throw new ArgumentNullException(nameof(statisticsMenu)); + _healthCheckService = healthCheckService ?? throw new ArgumentNullException(nameof(healthCheckService)); } - public Task PrintAsync(CancellationToken cancellationToken) + public async Task PrintAsync(CancellationToken cancellationToken) { PrintGreeting(); + await _healthCheckService.ConnectAsync(); + cancellationToken.ThrowIfCancellationRequested(); + await Task.Factory.StartNew(() => _healthCheckService.PingAsync(), TaskCreationOptions.LongRunning); + cancellationToken.ThrowIfCancellationRequested(); + "\nPress any key to show start up menu list.".Print(ConsoleColor.Green); Console.ReadKey(); Console.Clear(); - //todo: trying to connect to the server - - return ShowStartAsync(cancellationToken); + + await ShowStartAsync(cancellationToken); } - + private async Task ShowStartAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) diff --git a/Client/Client.StartMenu/Services/HealthCheckService.cs b/Client/Client.StartMenu/Services/HealthCheckService.cs new file mode 100644 index 0000000..50f5753 --- /dev/null +++ b/Client/Client.StartMenu/Services/HealthCheckService.cs @@ -0,0 +1,51 @@ +using RockPaperScissors.Common.Client; +using RockPaperScissors.Common.Extensions; + +namespace Client.StartMenu.Services; + +internal sealed class HealthCheckService : IHealthCheckService +{ + private readonly IClient _client; + private readonly CancellationTokenSource _cancellationTokenSource; + + public HealthCheckService(IClient client, CancellationTokenSource cancellationTokenSource) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + _cancellationTokenSource = + cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); + } + + public async Task ConnectAsync() + { + "\nTrying to connect to the server...".Print(ConsoleColor.White); + var result = await _client.GetAsync("/health", _cancellationTokenSource.Token); + + if (result) + { + "Connected to the server".Print(ConsoleColor.Green); + + return; + } + + "Failed to connect. Closing... \nPress any key...".Print(ConsoleColor.Red); + _cancellationTokenSource.Cancel(); + } + + public async Task PingAsync() + { + "Starting health checks".Print(ConsoleColor.White); + while (!_cancellationTokenSource.IsCancellationRequested) + { + var result = await _client.GetAsync("/health", _cancellationTokenSource.Token); + + if (!result) + { + "\nConnection to the server lost. Stopping...\nPress any key...".Print(ConsoleColor.Red); + + _cancellationTokenSource.Cancel(); + } + + await Task.Delay(100); + } + } +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Services/IHealthCheckService.cs b/Client/Client.StartMenu/Services/IHealthCheckService.cs new file mode 100644 index 0000000..a5243b3 --- /dev/null +++ b/Client/Client.StartMenu/Services/IHealthCheckService.cs @@ -0,0 +1,8 @@ +namespace Client.StartMenu.Services; + +public interface IHealthCheckService +{ + Task ConnectAsync(); + + Task PingAsync(); +} \ No newline at end of file diff --git a/Client/Client.Statistics/Menus/StatisticsMenu.cs b/Client/Client.Statistics/Menus/StatisticsMenu.cs index 1fa269d..5e68301 100644 --- a/Client/Client.Statistics/Menus/StatisticsMenu.cs +++ b/Client/Client.Statistics/Menus/StatisticsMenu.cs @@ -23,23 +23,23 @@ public async Task StartAsync(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { "Statistics Menu:".Print(ConsoleColor.DarkYellow); - $"{MenuTypes.All.GetValue()}.\t{MenuTypes.All.GetDisplayName()}".Print(ConsoleColor.DarkYellow); $"{MenuTypes.Personal.GetValue()}.\t{MenuTypes.Personal.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + $"{MenuTypes.All.GetValue()}.\t{MenuTypes.All.GetDisplayName()}".Print(ConsoleColor.DarkYellow); $"{MenuTypes.Back.GetValue()}.\t{MenuTypes.Back.GetDisplayName()}".Print(ConsoleColor.DarkYellow); "\nPlease select an item from the list".Print(ConsoleColor.Green); Console.Write("Select -> "); - + var menuType = Console.ReadLine().TryGetMenuType(); - + if (menuType is MenuTypes.Unknown) { "Invalid input. Try again.".Print(ConsoleColor.Red); continue; } - + switch (menuType) { case MenuTypes.All: diff --git a/Common/RockPaperScissors.Common/Client/Client.cs b/Common/RockPaperScissors.Common/Client/Client.cs index e692ec7..6e33849 100644 --- a/Common/RockPaperScissors.Common/Client/Client.cs +++ b/Common/RockPaperScissors.Common/Client/Client.cs @@ -81,6 +81,31 @@ public async Task> GetAsync( return (await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken))!; } + + public async Task GetAsync( + string url, + CancellationToken cancellationToken) + { + try + { + using var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(HttpClient.BaseAddress!, url), + Version = HttpClient.DefaultRequestVersion, + VersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + using var response = + await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + return response.IsSuccessStatusCode; + } + catch + { + return false; + } + } public async Task> PostAsync( string url, diff --git a/Common/RockPaperScissors.Common/Client/IClient.cs b/Common/RockPaperScissors.Common/Client/IClient.cs index f994ce8..f7f7f1f 100644 --- a/Common/RockPaperScissors.Common/Client/IClient.cs +++ b/Common/RockPaperScissors.Common/Client/IClient.cs @@ -13,6 +13,10 @@ Task> GetAsync( (string HeaderKey, string HeaderValue) headerValues, CancellationToken cancellationToken = default); + Task GetAsync( + string url, + CancellationToken cancellationToken); + Task> PostAsync( string url, T1 content, diff --git a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs index b8a71d6..9c39c05 100644 --- a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,6 @@ using System; using Mapster; using Microsoft.Extensions.DependencyInjection; -using Server.Bll.HostedServices; using Server.Bll.Models; using Server.Bll.Services; using Server.Bll.Services.Interfaces; diff --git a/Server/Server.Bll/HostedServices/CleanerHostedService.cs b/Server/Server.Bll/Services/CleanerHostedService.cs similarity index 97% rename from Server/Server.Bll/HostedServices/CleanerHostedService.cs rename to Server/Server.Bll/Services/CleanerHostedService.cs index 645398c..43bde08 100644 --- a/Server/Server.Bll/HostedServices/CleanerHostedService.cs +++ b/Server/Server.Bll/Services/CleanerHostedService.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; using Server.Bll.Services.Interfaces; -namespace Server.Bll.HostedServices; +namespace Server.Bll.Services; public sealed class CleanerHostedService : IHostedService { diff --git a/Server/Server.Host/Startup.cs b/Server/Server.Host/Startup.cs index 90f9384..1c3a8a0 100644 --- a/Server/Server.Host/Startup.cs +++ b/Server/Server.Host/Startup.cs @@ -23,6 +23,8 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { + services.AddHealthChecks(); + services .AddDatabase(Configuration) .AddSwagger() @@ -42,6 +44,7 @@ public static void Configure( { serverContext.Database.Migrate(); serverContext?.EnsureBotCreated(); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -59,8 +62,10 @@ public static void Configure( app.UseAuthorization(); + app.UseEndpoints(endpoints => { + endpoints.MapHealthChecks("/health"); endpoints.MapControllers(); }); } From 1062f21d3c1d0b7cde14997c6693dcae20847379 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 16 Oct 2022 15:14:59 +0300 Subject: [PATCH 50/56] Added mock for Main menu logic --- .../Extensions/ServiceCollectionExtensions.cs | 1 + Client/Client.Account/Menus/AccountMenu.cs | 16 ++++-- .../Client.Account/Services/AccountService.cs | 3 +- .../{ => Interfaces}/IAccountService.cs | 2 +- .../Services/{ => Interfaces}/IUser.cs | 2 +- Client/Client.Account/Services/User.cs | 4 +- Client/Client.MainMenu/Client.MainMenu.csproj | 13 +++++ Client/Client.MainMenu/Enums/MenuTypes.cs | 10 ++++ .../Extensions/EnumExtensions.cs | 42 +++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 14 +++++ .../Services/LongPollingService.cs | 11 ++++ .../Client.MainMenu/Services/RoomService.cs | 11 ++++ .../Client.MainMenu/Services/RoundService.cs | 11 ++++ Client/Client.StartMenu/Menus/StartMenu.cs | 13 ++--- .../Services/HealthCheckService.cs | 23 ++++---- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Client.Statistics/Menus/StatisticsMenu.cs | 54 +++++++++++-------- .../Services/StatisticsService.cs | 5 +- RockPaperScissorsGame.sln | 7 +++ 19 files changed, 195 insertions(+), 48 deletions(-) rename Client/Client.Account/Services/{ => Interfaces}/IAccountService.cs (93%) rename Client/Client.Account/Services/{ => Interfaces}/IUser.cs (82%) create mode 100644 Client/Client.MainMenu/Client.MainMenu.csproj create mode 100644 Client/Client.MainMenu/Enums/MenuTypes.cs create mode 100644 Client/Client.MainMenu/Extensions/EnumExtensions.cs create mode 100644 Client/Client.MainMenu/Extensions/ServiceCollectionExtensions.cs create mode 100644 Client/Client.MainMenu/Services/LongPollingService.cs create mode 100644 Client/Client.MainMenu/Services/RoomService.cs create mode 100644 Client/Client.MainMenu/Services/RoundService.cs diff --git a/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs b/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs index cfb5b54..476abe6 100644 --- a/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs +++ b/Client/Client.Account/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Client.Account.Menus; using Client.Account.Services; +using Client.Account.Services.Interfaces; using RockPaperScissors.Common.Client; namespace Client.Account.Extensions; diff --git a/Client/Client.Account/Menus/AccountMenu.cs b/Client/Client.Account/Menus/AccountMenu.cs index 5fa4b31..b467d1f 100644 --- a/Client/Client.Account/Menus/AccountMenu.cs +++ b/Client/Client.Account/Menus/AccountMenu.cs @@ -1,6 +1,7 @@ using Client.Account.Enums; using Client.Account.Extensions; using Client.Account.Services; +using Client.Account.Services.Interfaces; using RockPaperScissors.Common.Enums; using RockPaperScissors.Common.Extensions; @@ -52,24 +53,29 @@ public async Task StartAsync(CancellationToken cancellationToken) } "Trying logging in...".Print(ConsoleColor.White); + Console.Clear(); return; case MenuTypes.Login: var isSuccess = await LoginAsync(cancellationToken); - + if (isSuccess) { + "\nPress any key to back to the start up menu list!".Print(ConsoleColor.Cyan); + Console.ReadKey(); + Console.Clear(); + return; } - - Console.Clear(); - + continue; case MenuTypes.Back: + Console.Clear(); + return; - + default: "Invalid input. Try again.".Print(ConsoleColor.Red); continue; diff --git a/Client/Client.Account/Services/AccountService.cs b/Client/Client.Account/Services/AccountService.cs index 5ab9aeb..db08747 100644 --- a/Client/Client.Account/Services/AccountService.cs +++ b/Client/Client.Account/Services/AccountService.cs @@ -1,4 +1,5 @@ -using OneOf; +using Client.Account.Services.Interfaces; +using OneOf; using RockPaperScissors.Common; using RockPaperScissors.Common.Client; using RockPaperScissors.Common.Extensions; diff --git a/Client/Client.Account/Services/IAccountService.cs b/Client/Client.Account/Services/Interfaces/IAccountService.cs similarity index 93% rename from Client/Client.Account/Services/IAccountService.cs rename to Client/Client.Account/Services/Interfaces/IAccountService.cs index 42ebac5..0354256 100644 --- a/Client/Client.Account/Services/IAccountService.cs +++ b/Client/Client.Account/Services/Interfaces/IAccountService.cs @@ -2,7 +2,7 @@ using RockPaperScissors.Common; using RockPaperScissors.Common.Responses; -namespace Client.Account.Services; +namespace Client.Account.Services.Interfaces; public interface IAccountService { diff --git a/Client/Client.Account/Services/IUser.cs b/Client/Client.Account/Services/Interfaces/IUser.cs similarity index 82% rename from Client/Client.Account/Services/IUser.cs rename to Client/Client.Account/Services/Interfaces/IUser.cs index ca16f20..998c032 100644 --- a/Client/Client.Account/Services/IUser.cs +++ b/Client/Client.Account/Services/Interfaces/IUser.cs @@ -1,4 +1,4 @@ -namespace Client.Account.Services; +namespace Client.Account.Services.Interfaces; public interface IUser { diff --git a/Client/Client.Account/Services/User.cs b/Client/Client.Account/Services/User.cs index 0d47652..39f5692 100644 --- a/Client/Client.Account/Services/User.cs +++ b/Client/Client.Account/Services/User.cs @@ -1,4 +1,6 @@ -namespace Client.Account.Services; +using Client.Account.Services.Interfaces; + +namespace Client.Account.Services; internal sealed class User : IUser { diff --git a/Client/Client.MainMenu/Client.MainMenu.csproj b/Client/Client.MainMenu/Client.MainMenu.csproj new file mode 100644 index 0000000..09a039c --- /dev/null +++ b/Client/Client.MainMenu/Client.MainMenu.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Client/Client.MainMenu/Enums/MenuTypes.cs b/Client/Client.MainMenu/Enums/MenuTypes.cs new file mode 100644 index 0000000..adf197d --- /dev/null +++ b/Client/Client.MainMenu/Enums/MenuTypes.cs @@ -0,0 +1,10 @@ +namespace Client.MainMenu.Enums; + +internal enum MenuTypes +{ + Unknown = 0, + PersonalScoreboard = 1, + CreateRoom = 2, + Logout = 3, + Exit = 4, +} \ No newline at end of file diff --git a/Client/Client.MainMenu/Extensions/EnumExtensions.cs b/Client/Client.MainMenu/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..0fafffd --- /dev/null +++ b/Client/Client.MainMenu/Extensions/EnumExtensions.cs @@ -0,0 +1,42 @@ +using Client.MainMenu.Enums; + +namespace Client.MainMenu.Extensions; + +internal static class EnumExtensions +{ + internal static string GetDisplayName(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.PersonalScoreboard => "Personal Statistics", + MenuTypes.CreateRoom => "Create room", + MenuTypes.Logout => "Logout", + MenuTypes.Exit => "Exit", + _ => "Unknown", + }; + } + + internal static MenuTypes TryGetMenuType(this string? stringInput) + { + return stringInput switch + { + "4" => MenuTypes.Exit, + "3" => MenuTypes.Logout, + "2" => MenuTypes.CreateRoom, + "1" => MenuTypes.PersonalScoreboard, + _ => MenuTypes.Unknown, + }; + } + + internal static int GetValue(this MenuTypes menuTypes) + { + return menuTypes switch + { + MenuTypes.Exit => 4, + MenuTypes.Logout => 3, + MenuTypes.CreateRoom => 2, + MenuTypes.PersonalScoreboard => 1, + _ => 0, + }; + } +} \ No newline at end of file diff --git a/Client/Client.MainMenu/Extensions/ServiceCollectionExtensions.cs b/Client/Client.MainMenu/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..6001356 --- /dev/null +++ b/Client/Client.MainMenu/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,14 @@ +namespace Client.MainMenu.Extensions; + +// public static class ServiceCollectionExtensions +// { +// public static IAccountService CreateAccountService(this IClient client) +// { +// return new AccountService(client); +// } +// +// public static IAccountMenu CreateAccountMenu(this IAccountService accountService) +// { +// return new AccountMenu(accountService); +// } +// } \ No newline at end of file diff --git a/Client/Client.MainMenu/Services/LongPollingService.cs b/Client/Client.MainMenu/Services/LongPollingService.cs new file mode 100644 index 0000000..7af7a87 --- /dev/null +++ b/Client/Client.MainMenu/Services/LongPollingService.cs @@ -0,0 +1,11 @@ +namespace Client.MainMenu.Services; + +public interface ILongPollingService +{ + //Todo : implement +} + +internal sealed class LongPollingService : ILongPollingService +{ + //Todo : implement +} \ No newline at end of file diff --git a/Client/Client.MainMenu/Services/RoomService.cs b/Client/Client.MainMenu/Services/RoomService.cs new file mode 100644 index 0000000..d0b0218 --- /dev/null +++ b/Client/Client.MainMenu/Services/RoomService.cs @@ -0,0 +1,11 @@ +namespace Client.MainMenu.Services; + +public interface IRoomService +{ + //Todo : implement +} + +internal sealed class RoomService : IRoomService +{ + //Todo : implement +} \ No newline at end of file diff --git a/Client/Client.MainMenu/Services/RoundService.cs b/Client/Client.MainMenu/Services/RoundService.cs new file mode 100644 index 0000000..2dbfc6a --- /dev/null +++ b/Client/Client.MainMenu/Services/RoundService.cs @@ -0,0 +1,11 @@ +namespace Client.MainMenu.Services; + +public interface IRoundService +{ + //Todo : implement +} + +internal sealed class RoundService : IRoundService +{ + //Todo : implement +} \ No newline at end of file diff --git a/Client/Client.StartMenu/Menus/StartMenu.cs b/Client/Client.StartMenu/Menus/StartMenu.cs index 82d4ea0..7053a10 100644 --- a/Client/Client.StartMenu/Menus/StartMenu.cs +++ b/Client/Client.StartMenu/Menus/StartMenu.cs @@ -29,9 +29,8 @@ public async Task PrintAsync(CancellationToken cancellationToken) await _healthCheckService.ConnectAsync(); cancellationToken.ThrowIfCancellationRequested(); - await Task.Factory.StartNew(() => _healthCheckService.PingAsync(), TaskCreationOptions.LongRunning); - cancellationToken.ThrowIfCancellationRequested(); - + await _healthCheckService.PingAsync(); + "\nPress any key to show start up menu list.".Print(ConsoleColor.Green); Console.ReadKey(); @@ -67,17 +66,19 @@ private async Task ShowStartAsync(CancellationToken cancellationToken) case MenuTypes.Account: await _accountMenu.StartAsync(cancellationToken); Console.Clear(); - + break; case MenuTypes.Leaderboard: await _statisticsMenu.StartAsync(cancellationToken); - + Console.Clear(); + break; case MenuTypes.Exit: await _accountMenu.LogoutAsync(cancellationToken); - + Console.Clear(); + return; default: diff --git a/Client/Client.StartMenu/Services/HealthCheckService.cs b/Client/Client.StartMenu/Services/HealthCheckService.cs index 50f5753..2a45c0f 100644 --- a/Client/Client.StartMenu/Services/HealthCheckService.cs +++ b/Client/Client.StartMenu/Services/HealthCheckService.cs @@ -33,19 +33,22 @@ public async Task ConnectAsync() public async Task PingAsync() { - "Starting health checks".Print(ConsoleColor.White); - while (!_cancellationTokenSource.IsCancellationRequested) + await Task.Factory.StartNew(async () => { - var result = await _client.GetAsync("/health", _cancellationTokenSource.Token); - - if (!result) + "Starting health checks".Print(ConsoleColor.White); + while (!_cancellationTokenSource.IsCancellationRequested) { - "\nConnection to the server lost. Stopping...\nPress any key...".Print(ConsoleColor.Red); + var result = await _client.GetAsync("/health", _cancellationTokenSource.Token); + + if (!result) + { + "\nConnection to the server lost. Stopping...\nPress any key...".Print(ConsoleColor.Red); - _cancellationTokenSource.Cancel(); - } + _cancellationTokenSource.Cancel(); + } - await Task.Delay(100); - } + await Task.Delay(1000); + } + }, TaskCreationOptions.LongRunning); } } \ No newline at end of file diff --git a/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs b/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs index f0d24fa..a9b214c 100644 --- a/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs +++ b/Client/Client.Statistics/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Client.Account.Services; +using Client.Account.Services.Interfaces; using Client.Statistics.Menus; using Client.Statistics.Services; using RockPaperScissors.Common.Client; diff --git a/Client/Client.Statistics/Menus/StatisticsMenu.cs b/Client/Client.Statistics/Menus/StatisticsMenu.cs index 5e68301..f40225e 100644 --- a/Client/Client.Statistics/Menus/StatisticsMenu.cs +++ b/Client/Client.Statistics/Menus/StatisticsMenu.cs @@ -1,4 +1,5 @@ using Client.Account.Services; +using Client.Account.Services.Interfaces; using Client.Statistics.EnumExtensions; using Client.Statistics.Enums; using Client.Statistics.Services; @@ -23,7 +24,12 @@ public async Task StartAsync(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { "Statistics Menu:".Print(ConsoleColor.DarkYellow); - $"{MenuTypes.Personal.GetValue()}.\t{MenuTypes.Personal.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + + if (_accountService.IsAuthorized()) + { + $"{MenuTypes.Personal.GetValue()}.\t{MenuTypes.Personal.GetDisplayName()}".Print(ConsoleColor.DarkYellow); + } + $"{MenuTypes.All.GetValue()}.\t{MenuTypes.All.GetDisplayName()}".Print(ConsoleColor.DarkYellow); $"{MenuTypes.Back.GetValue()}.\t{MenuTypes.Back.GetDisplayName()}".Print(ConsoleColor.DarkYellow); @@ -43,18 +49,22 @@ public async Task StartAsync(CancellationToken cancellationToken) switch (menuType) { case MenuTypes.All: + Console.Clear(); await PrintAllStatisticsAsync(cancellationToken); - + break; - + case MenuTypes.Personal: + Console.Clear(); await PrintPersonalStatisticsAsync(cancellationToken); - + break; - + case MenuTypes.Back: + Console.Clear(); + return; - + default: "Invalid input. Try again.".Print(ConsoleColor.Red); continue; @@ -72,7 +82,7 @@ private async Task PrintAllStatisticsAsync(CancellationToken cancellationToken) return; } - + allStatistics.AsT1.Message.Print(ConsoleColor.Red); } @@ -81,23 +91,23 @@ private async Task PrintPersonalStatisticsAsync(CancellationToken cancellationTo if (!_accountService.IsAuthorized()) { "User in not logged in.".Print(ConsoleColor.Red); - + return; } - + var user = _accountService.GetUser(); - var personalStatistics = await _statisticsService.GetPersonalAsync(user.SessionId, cancellationToken); - + var personalStatistics = await _statisticsService.GetPersonalAsync(user.GetBearerToken(), cancellationToken); + if (personalStatistics.IsT0) { PrintStatistics(personalStatistics.AsT0, user.Login!); return; } - + personalStatistics.AsT1.Message.Print(ConsoleColor.Red); } - + private static void PrintAllStatistics(AllStatisticsResponse[] allStatistics) { var statisticsSpan = allStatistics.AsSpan(); @@ -108,20 +118,20 @@ private static void PrintAllStatistics(AllStatisticsResponse[] allStatistics) $"{index + 1}. User: {statisticsSpan[index].Login}; Score: {statisticsSpan[index].Score}".Print(color); } } - + private static void PrintStatistics(PersonalStatisticsResponse allStatistics, string userName) { - $"Here is your statistics, {userName}:".Print(ConsoleColor.White); - - "Main statistics: ".Print(ConsoleColor.DarkYellow); - + $"Here is your statistics, \"{userName}\" :".Print(ConsoleColor.White); + + "\tMain statistics".Print(ConsoleColor.DarkYellow); + $"\t{nameof(allStatistics.Wins)}: {allStatistics.Wins}".Print(ConsoleColor.Green); $"\t{nameof(allStatistics.Draws)}: {allStatistics.Draws}".Print(ConsoleColor.Yellow); $"\t{nameof(allStatistics.Loss)}: {allStatistics.Loss}".Print(ConsoleColor.Red); $"\t{nameof(allStatistics.Score)}: {allStatistics.Score}".Print(ConsoleColor.White); - - "Other statistics: ".Print(ConsoleColor.DarkYellow); - + + "\tOther statistics".Print(ConsoleColor.DarkYellow); + $"\t{allStatistics.TimeSpent} spent time playing".Print(ConsoleColor.White); $"\t{allStatistics.UsedPaper} times used 'Paper'".Print(ConsoleColor.White); $"\t{allStatistics.UsedRock} times used 'Rock'".Print(ConsoleColor.White); @@ -138,7 +148,7 @@ private static ConsoleColor GetColor(int index) _ => ConsoleColor.White }; } - + private static ConsoleColor GetColor(double index) { return index switch diff --git a/Client/Client.Statistics/Services/StatisticsService.cs b/Client/Client.Statistics/Services/StatisticsService.cs index 7de64cf..7780151 100644 --- a/Client/Client.Statistics/Services/StatisticsService.cs +++ b/Client/Client.Statistics/Services/StatisticsService.cs @@ -29,6 +29,9 @@ public Task> GetPersonalAsync return Task.FromResult(OneOf.FromT1(new CustomException("Token must not be 'null' or '\"\"'"))); } - throw new NotImplementedException(); + var response = + _client.GetAsync("api/Statistics/personal", ("Authorization", token), cancellationToken); + + return response; } } \ No newline at end of file diff --git a/RockPaperScissorsGame.sln b/RockPaperScissorsGame.sln index 921fd26..3a2d5c2 100644 --- a/RockPaperScissorsGame.sln +++ b/RockPaperScissorsGame.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RockPaperScissors.Common", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Statistics", "Client\Client.Statistics\Client.Statistics.csproj", "{994CC558-A9A9-4E58-A894-EE06993DAC58}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.MainMenu", "Client\Client.MainMenu\Client.MainMenu.csproj", "{3928C9ED-4F7C-4D3E-8265-C98EBC85F318}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {994CC558-A9A9-4E58-A894-EE06993DAC58}.Debug|Any CPU.Build.0 = Debug|Any CPU {994CC558-A9A9-4E58-A894-EE06993DAC58}.Release|Any CPU.ActiveCfg = Release|Any CPU {994CC558-A9A9-4E58-A894-EE06993DAC58}.Release|Any CPU.Build.0 = Release|Any CPU + {3928C9ED-4F7C-4D3E-8265-C98EBC85F318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3928C9ED-4F7C-4D3E-8265-C98EBC85F318}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3928C9ED-4F7C-4D3E-8265-C98EBC85F318}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3928C9ED-4F7C-4D3E-8265-C98EBC85F318}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -83,5 +89,6 @@ Global {32045C10-4395-4010-A365-6CAF4D993680} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} {BB2630C0-3F25-49F1-9393-4B75A7014187} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} {994CC558-A9A9-4E58-A894-EE06993DAC58} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} + {3928C9ED-4F7C-4D3E-8265-C98EBC85F318} = {540EE2A5-907D-4E6A-A051-22D01DBFE913} EndGlobalSection EndGlobal From 4efaf4857957fde28919f4703e889906737a67ab Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 16 Oct 2022 16:04:52 +0300 Subject: [PATCH 51/56] Added constant templates --- .../RockPaperScissors.Common/UrlTemplates.cs | 26 +++++++++++++++++++ .../Controllers/AccountController.cs | 8 +++--- .../Controllers/LongPollingController.cs | 6 ++--- .../Server.Host/Controllers/RoomController.cs | 16 ++++++------ .../Controllers/RoundController.cs | 6 ++--- .../Controllers/StatisticsController.cs | 5 ++-- 6 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 Common/RockPaperScissors.Common/UrlTemplates.cs diff --git a/Common/RockPaperScissors.Common/UrlTemplates.cs b/Common/RockPaperScissors.Common/UrlTemplates.cs new file mode 100644 index 0000000..e4f2925 --- /dev/null +++ b/Common/RockPaperScissors.Common/UrlTemplates.cs @@ -0,0 +1,26 @@ +namespace RockPaperScissors.Common; + +public static class UrlTemplates +{ + // Account-related + public const string RegisterUrl = "api/account/register"; + public const string LoginUrl = "api/account/login"; + public const string LogoutUrl = "api/account/logout"; + + // Statistics-related + public const string AllStatistics = "api/statistics/all"; + public const string PersonalStatistics = "api/statistics/personal"; + + // Room-related + public const string CreateRoom = "api/room/create"; + public const string JoinPublicRoom = "api/room/public/join"; + public const string JoinPrivateRoom = "api/room/private/{roomCode}/join"; + public const string UpdateRoom = "api/room/update"; + public const string DeleteRoom = "api/room/delete"; + public const string CheckRoomUpdateTicks = "api/room/{roomId}/status"; + + // Round-related + public const string CreateRound = "api/round/create"; + public const string UpdateRound = "api/round/update"; + public const string CheckRoundUpdateTicks = "api/round/{roomId}/status"; +} \ No newline at end of file diff --git a/Server/Server.Host/Controllers/AccountController.cs b/Server/Server.Host/Controllers/AccountController.cs index ddb9a1c..fe2666e 100644 --- a/Server/Server.Host/Controllers/AccountController.cs +++ b/Server/Server.Host/Controllers/AccountController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using RockPaperScissors.Common; using RockPaperScissors.Common.Models; using RockPaperScissors.Common.Requests; using Server.Authentication.Exceptions; @@ -14,7 +15,6 @@ namespace Server.Host.Controllers; [ApiController] -[Route ("api/[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] public sealed class AccountController: ControllerBase @@ -26,7 +26,7 @@ public AccountController(IAuthService authService) _authService = authService ?? throw new ArgumentNullException(nameof(authService)); } - [HttpPost("register")] + [HttpPost(UrlTemplates.RegisterUrl)] [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] public async Task RegisterAsync(RegisterRequest registerRequest) @@ -39,7 +39,7 @@ public async Task RegisterAsync(RegisterRequest registerRequest) userException => BadRequest(userException)); } - [HttpPost("login")] + [HttpPost(UrlTemplates.LoginUrl)] [ProducesResponseType(typeof(AccountOutputModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] public async Task LoginAsync(AccountDto accountDto) @@ -53,7 +53,7 @@ public async Task LoginAsync(AccountDto accountDto) } [Authorize] - [HttpGet("logout")] + [HttpGet(UrlTemplates.LogoutUrl)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] public ActionResult Logout(string sessionId) diff --git a/Server/Server.Host/Controllers/LongPollingController.cs b/Server/Server.Host/Controllers/LongPollingController.cs index 324284b..517feb7 100644 --- a/Server/Server.Host/Controllers/LongPollingController.cs +++ b/Server/Server.Host/Controllers/LongPollingController.cs @@ -4,12 +4,12 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using RockPaperScissors.Common; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; [ApiController] -[Route ("api")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class LongPollingController : ControllerBase { @@ -20,14 +20,14 @@ public LongPollingController(ILongPollingService longPollingService) _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } - [HttpGet("Room/{roomId}/status")] + [HttpGet(UrlTemplates.CheckRoomUpdateTicks)] [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] public Task CheckRoomUpdateTicksAsync(string roomId) { return _longPollingService.GetRoomUpdateTicksAsync(roomId); } - [HttpGet("Round/{roundId}/status")] + [HttpGet(UrlTemplates.CheckRoundUpdateTicks)] [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] public Task CheckRoundUpdateTicksAsync(string roundId) { diff --git a/Server/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs index 2f05b7e..c525c90 100644 --- a/Server/Server.Host/Controllers/RoomController.cs +++ b/Server/Server.Host/Controllers/RoomController.cs @@ -5,13 +5,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; [ApiController] -[Route ("api/[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -26,12 +26,12 @@ public RoomController(IRoomService roomService) private string UserId => User.Identity?.Name ?? string.Empty; - [HttpPost("create")] + [HttpPost(UrlTemplates.CreateRoom)] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateAsync( [FromQuery] bool isPrivate, - [FromHeader(Name="X-Training")] bool isTraining = false) + [FromQuery] bool isTraining = false) { var newRoom = await _roomService .CreateAsync(UserId, isPrivate, isTraining); @@ -41,7 +41,7 @@ public async Task CreateAsync( exception => BadRequest(exception)); } - [HttpPost("join/public")] + [HttpPost(UrlTemplates.JoinPublicRoom)] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task JoinPublicAsync() @@ -53,10 +53,10 @@ public async Task JoinPublicAsync() exception => BadRequest(exception)); } - [HttpPost("join/private")] + [HttpPost(UrlTemplates.JoinPrivateRoom)] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task JoinPrivateAsync([FromQuery] string roomCode) + public async Task JoinPrivateAsync(string roomCode) { var result = await _roomService.JoinAsync(UserId, roomCode); @@ -65,7 +65,7 @@ public async Task JoinPrivateAsync([FromQuery] string roomCode) exception => BadRequest(exception)); } - [HttpGet("update")] + [HttpPost(UrlTemplates.UpdateRoom)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task UpdateAsync([FromBody] RoomModel roomModel) @@ -79,7 +79,7 @@ public async Task UpdateAsync([FromBody] RoomModel roomModel) }; } - [HttpDelete("delete")] + [HttpDelete(UrlTemplates.DeleteRoom)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task DeleteAsync([FromQuery] string roomId) diff --git a/Server/Server.Host/Controllers/RoundController.cs b/Server/Server.Host/Controllers/RoundController.cs index 7a35366..3f97e6e 100644 --- a/Server/Server.Host/Controllers/RoundController.cs +++ b/Server/Server.Host/Controllers/RoundController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -14,7 +15,6 @@ namespace Server.Host.Controllers; /// API Round Controller /// [ApiController] -[Route ("api/[controller]")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoundController: ControllerBase @@ -33,7 +33,7 @@ public RoundController(IRoundService roundService) /// /// id of the room /// - [HttpPost("create")] + [HttpPost(UrlTemplates.CreateRound)] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task CreateRound(int roomId) @@ -49,7 +49,7 @@ public async Task CreateRound(int roomId) /// /// This round model from FE or client. /// - [HttpPatch("update")] + [HttpPatch(UrlTemplates.UpdateRound)] //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task UpdateCurrentRound(RoundModel roundModel) diff --git a/Server/Server.Host/Controllers/StatisticsController.cs b/Server/Server.Host/Controllers/StatisticsController.cs index 65a124e..ce236ed 100644 --- a/Server/Server.Host/Controllers/StatisticsController.cs +++ b/Server/Server.Host/Controllers/StatisticsController.cs @@ -12,7 +12,6 @@ namespace Server.Host.Controllers; [ApiController] -[Route ("api/[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -28,7 +27,7 @@ public StatisticsController(IStatisticsService statisticsService) private string UserId => User.Identity?.Name ?? string.Empty; [AllowAnonymous] - [HttpGet("all")] + [HttpGet(UrlTemplates.AllStatistics)] [ProducesResponseType(typeof(ShortStatisticsModel[]), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public Task GetOverallStatistics() @@ -37,7 +36,7 @@ public Task GetOverallStatistics() } [Authorize] - [HttpGet("personal")] + [HttpGet(UrlTemplates.PersonalStatistics)] [ProducesResponseType(typeof(StatisticsModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(CustomException), StatusCodes.Status400BadRequest)] public async Task GetPersonalStatistics() From f3a2b529f8b66c70482c47d15d4b73d21e1e4b2a Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 23 Oct 2022 21:01:54 +0300 Subject: [PATCH 52/56] Swagger fixes --- .../RockPaperScissors.Common/UrlTemplates.cs | 2 +- Server/Server.Bll/Models/PlayerModel.cs | 14 ++---- Server/Server.Bll/Models/RoomModel.cs | 19 +++++--- Server/Server.Bll/Models/RoundModel.cs | 20 +++----- Server/Server.Bll/Services/RoomService.cs | 35 ++++++++------ Server/Server.Bll/Services/RoundService.cs | 18 ++++++++ .../Server.Bll/Services/StatisticsService.cs | 2 - Server/Server.Data/Entities/Player.cs | 2 + Server/Server.Data/Entities/Round.cs | 4 -- .../Extensions/SeedingExtension.cs | 23 ++++++++-- ...221023171541_InitialMigration.Designer.cs} | 27 ++--------- ....cs => 20221023171541_InitialMigration.cs} | 23 +--------- .../Controllers/AccountController.cs | 16 +++---- .../Server.Host/Controllers/ControllerBase.cs | 12 +++++ .../Controllers/LongPollingController.cs | 6 +-- .../Server.Host/Controllers/RoomController.cs | 25 ++++------ .../Controllers/RoundController.cs | 5 -- .../Controllers/StatisticsController.cs | 12 ++--- .../Extensions/SwaggerExtension.cs | 46 ++++++++++++++----- Server/Server.Host/Server.Host.csproj | 3 ++ Server/Server.Host/Startup.cs | 2 +- 21 files changed, 158 insertions(+), 158 deletions(-) rename Server/Server.Data/Migrations/{20221015192454_InitialMigration.Designer.cs => 20221023171541_InitialMigration.Designer.cs} (90%) rename Server/Server.Data/Migrations/{20221015192454_InitialMigration.cs => 20221023171541_InitialMigration.cs} (87%) create mode 100644 Server/Server.Host/Controllers/ControllerBase.cs diff --git a/Common/RockPaperScissors.Common/UrlTemplates.cs b/Common/RockPaperScissors.Common/UrlTemplates.cs index e4f2925..0d9fdbc 100644 --- a/Common/RockPaperScissors.Common/UrlTemplates.cs +++ b/Common/RockPaperScissors.Common/UrlTemplates.cs @@ -22,5 +22,5 @@ public static class UrlTemplates // Round-related public const string CreateRound = "api/round/create"; public const string UpdateRound = "api/round/update"; - public const string CheckRoundUpdateTicks = "api/round/{roomId}/status"; + public const string CheckRoundUpdateTicks = "api/round/{roundId}/status"; } \ No newline at end of file diff --git a/Server/Server.Bll/Models/PlayerModel.cs b/Server/Server.Bll/Models/PlayerModel.cs index 3b51bcc..17dafe7 100644 --- a/Server/Server.Bll/Models/PlayerModel.cs +++ b/Server/Server.Bll/Models/PlayerModel.cs @@ -3,15 +3,11 @@ namespace Server.Bll.Models; public sealed class PlayerModel { - public PlayerModel( - string id, - int move) - { - Id = id; - Move = move; - } - public string Id { get; init; } - public int Move { get; set; } + public int Move { get; init; } + + public bool IsReady { get; init; } + + public bool IsWinner { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Models/RoomModel.cs b/Server/Server.Bll/Models/RoomModel.cs index cbb5cec..42d7c0b 100644 --- a/Server/Server.Bll/Models/RoomModel.cs +++ b/Server/Server.Bll/Models/RoomModel.cs @@ -12,30 +12,35 @@ public sealed class RoomModel /// /// Special code to join a room /// - public string Code { get; set; } + public string Code { get; init; } /// /// Round, linked to this room /// - public RoundModel? Round { get; set; } + public RoundModel? Round { get; init; } /// - /// . + /// . /// - public ICollection? Players { get; set; } + public ICollection? Players { get; init; } /// /// Flag is this room is private /// - public bool IsPrivate { get; set; } + public bool IsPrivate { get; init; } /// /// Flag if room is full /// - public bool IsFull { get; set; } + public bool IsFull { get; init; } /// /// Creation date. After 5 minutes of inactivity will be deleted /// - public long CreationTimeTicks { get; set; } + public long CreationTimeTicks { get; init; } + + /// + /// Last update time ticks. + /// + public long UpdateTicks { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Models/RoundModel.cs b/Server/Server.Bll/Models/RoundModel.cs index e9a01ba..100e614 100644 --- a/Server/Server.Bll/Models/RoundModel.cs +++ b/Server/Server.Bll/Models/RoundModel.cs @@ -1,20 +1,14 @@ -using System; - -namespace Server.Bll.Models; +namespace Server.Bll.Models; public sealed class RoundModel { - public int Id { get; set; } - - public bool IsFinished { get; set; } - - public DateTimeOffset TimeFinished { get; set; } + public string Id { get; init; } - public AccountModel Winner { get; set; } - - public AccountModel Loser { get; set; } + public bool IsFinished { get; init; } + + public long StartTimeTicks { get; init; } - public int FirstPlayerMove { get; set; } + public long FinishTimeTicks { get; init; } - public int SecondPlayerMove { get; set; } + public long UpdateTicks { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Services/RoomService.cs b/Server/Server.Bll/Services/RoomService.cs index 68efe84..ff4d507 100644 --- a/Server/Server.Bll/Services/RoomService.cs +++ b/Server/Server.Bll/Services/RoomService.cs @@ -18,13 +18,6 @@ namespace Server.Bll.Services; internal sealed class RoomService : IRoomService { - private static readonly Player BotPlayer = new() - { - Id = Guid.NewGuid().ToString(), - AccountId = SeedingExtension.BotId, - IsReady = true - }; - private readonly ServerContext _repository; public RoomService(ServerContext repository) @@ -38,7 +31,7 @@ public async Task> CreateAsync( var doesRoomExist = await _repository.Rooms .Include(room => room.Players) .AnyAsync(roomPlayers => roomPlayers.Players.Any(player => player.AccountId == userId)); - + if (doesRoomExist) { return new CustomException(ExceptionTemplates.TwinkRoom); @@ -49,11 +42,12 @@ public async Task> CreateAsync( new() { Id = Guid.NewGuid().ToString(), + Account = await _repository.Accounts.FindAsync(userId), AccountId = userId, IsReady = false, } }; - + var room = new Room { Id = Guid.NewGuid().ToString(), @@ -63,18 +57,23 @@ public async Task> CreateAsync( CreationTimeTicks = DateTimeOffset.UtcNow.Ticks, Players = players, UpdateTicks = DateTimeOffset.UtcNow.Ticks - }; + }; + + _repository.Rooms.Add(room); if (isTraining) { room.IsFull = true; - room.Players.Add(BotPlayer); + room.Players.Add(SeedingExtension.BotPlayer); } - - _repository.Add(room); - + + if (room.IsFull) + { + room.Round = RoundService.Create(room); + } + await _repository.SaveChangesAsync(); - + return room.Adapt(); } @@ -92,6 +91,7 @@ public async Task> JoinAsync(string userId, st var newPlayer = new Player { Id = Guid.NewGuid().ToString(), + Account = await _repository.Accounts.FindAsync(userId), AccountId = userId, IsReady = false, }; @@ -99,6 +99,11 @@ public async Task> JoinAsync(string userId, st room.Players.Add(newPlayer); room.UpdateTicks = DateTimeOffset.UtcNow.Ticks; room.IsFull = room.Players.Count is 2; + + if (room.IsFull) + { + room.Round = RoundService.Create(room); + } _repository.Rooms.Update(room); diff --git a/Server/Server.Bll/Services/RoundService.cs b/Server/Server.Bll/Services/RoundService.cs index 697d00e..782ff7b 100644 --- a/Server/Server.Bll/Services/RoundService.cs +++ b/Server/Server.Bll/Services/RoundService.cs @@ -119,4 +119,22 @@ public async Task> UpdateAsync(int userId, Rou // // return thisRound.Adapt(); } + + public static Round Create(Room room) + { + var currentTime = DateTimeOffset.UtcNow.Ticks; + var newRound = new Round + { + Id = Guid.NewGuid().ToString(), + RoomId = room.Id, + Room = room, + Players = room.Players, + IsFinished = false, + StartTimeTicks = currentTime, + FinishTimeTicks = 0, + UpdateTicks = currentTime + }; + + return newRound; + } } \ No newline at end of file diff --git a/Server/Server.Bll/Services/StatisticsService.cs b/Server/Server.Bll/Services/StatisticsService.cs index 5d3a6b4..efeba55 100644 --- a/Server/Server.Bll/Services/StatisticsService.cs +++ b/Server/Server.Bll/Services/StatisticsService.cs @@ -7,9 +7,7 @@ using Server.Bll.Models; using Server.Bll.Services.Interfaces; using OneOf; -using Server.Bll.Extensions; using Server.Data.Context; -using Server.Data.Entities; namespace Server.Bll.Services; diff --git a/Server/Server.Data/Entities/Player.cs b/Server/Server.Data/Entities/Player.cs index 322c408..d548ccc 100644 --- a/Server/Server.Data/Entities/Player.cs +++ b/Server/Server.Data/Entities/Player.cs @@ -18,4 +18,6 @@ public class Player public bool IsReady { get; set; } public int Move { get; set; } + + public bool IsWinner { get; set; } } \ No newline at end of file diff --git a/Server/Server.Data/Entities/Round.cs b/Server/Server.Data/Entities/Round.cs index ed82581..1a47a9d 100644 --- a/Server/Server.Data/Entities/Round.cs +++ b/Server/Server.Data/Entities/Round.cs @@ -17,10 +17,6 @@ public class Round public virtual Room Room { get; set; } public virtual ICollection Players { get; set; } - - public virtual Account Winner { get; set; } - - public virtual Account Loser { get; set; } public bool IsFinished { get; init; } diff --git a/Server/Server.Data/Extensions/SeedingExtension.cs b/Server/Server.Data/Extensions/SeedingExtension.cs index 8d0fbf7..bec0a3d 100644 --- a/Server/Server.Data/Extensions/SeedingExtension.cs +++ b/Server/Server.Data/Extensions/SeedingExtension.cs @@ -8,27 +8,42 @@ namespace Server.Data.Extensions; public static class SeedingExtension { + public static readonly Player BotPlayer = new() + { + Id = Guid.NewGuid().ToString(), + AccountId = BotId, + IsReady = true + }; + public const string BotId = "bot"; public static async Task EnsureBotCreated(this ServerContext context) { - if (await context.Accounts.ContainsAsync(new Account {Id = BotId})) + var bot = await context.Accounts.FindAsync(BotId); + if (bot is not null) { return; } - - context.Add(new Account + + var botAccount = new Account { Id = BotId, Login = BotId, Password = Guid.NewGuid().ToString() - }); + }; + + context.Add(botAccount); + + BotPlayer.Account = botAccount; context.Add(new Statistics { Id = BotId, + Account = botAccount, AccountId = BotId }); + + context.Add(BotPlayer); await context.SaveChangesAsync(); } diff --git a/Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs b/Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs similarity index 90% rename from Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs rename to Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs index dfb846b..0653c0e 100644 --- a/Server/Server.Data/Migrations/20221015192454_InitialMigration.Designer.cs +++ b/Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs @@ -11,7 +11,7 @@ namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20221015192454_InitialMigration")] + [Migration("20221023171541_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -46,6 +46,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsReady") .HasColumnType("INTEGER"); + b.Property("IsWinner") + .HasColumnType("INTEGER"); + b.Property("Move") .HasColumnType("INTEGER"); @@ -102,9 +105,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("LoserId") - .HasColumnType("TEXT"); - b.Property("RoomId") .HasColumnType("TEXT"); @@ -114,18 +114,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("UpdateTicks") .HasColumnType("INTEGER"); - b.Property("WinnerId") - .HasColumnType("TEXT"); - b.HasKey("Id"); - b.HasIndex("LoserId"); - b.HasIndex("RoomId") .IsUnique(); - b.HasIndex("WinnerId"); - b.ToTable("Round"); }); @@ -191,23 +184,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Data.Entities.Round", b => { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - b.HasOne("Server.Data.Entities.Room", "Room") .WithOne("Round") .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - b.Navigation("Room"); - - b.Navigation("Winner"); }); modelBuilder.Entity("Server.Data.Entities.Statistics", b => diff --git a/Server/Server.Data/Migrations/20221015192454_InitialMigration.cs b/Server/Server.Data/Migrations/20221023171541_InitialMigration.cs similarity index 87% rename from Server/Server.Data/Migrations/20221015192454_InitialMigration.cs rename to Server/Server.Data/Migrations/20221023171541_InitialMigration.cs index afa7da6..9e348f7 100644 --- a/Server/Server.Data/Migrations/20221015192454_InitialMigration.cs +++ b/Server/Server.Data/Migrations/20221023171541_InitialMigration.cs @@ -70,8 +70,6 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "TEXT", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), - WinnerId = table.Column(type: "TEXT", nullable: true), - LoserId = table.Column(type: "TEXT", nullable: true), IsFinished = table.Column(type: "INTEGER", nullable: false), StartTimeTicks = table.Column(type: "INTEGER", nullable: false), FinishTimeTicks = table.Column(type: "INTEGER", nullable: false), @@ -80,16 +78,6 @@ protected override void Up(MigrationBuilder migrationBuilder) constraints: table => { table.PrimaryKey("PK_Round", x => x.Id); - table.ForeignKey( - name: "FK_Round_Account_LoserId", - column: x => x.LoserId, - principalTable: "Account", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_Round_Account_WinnerId", - column: x => x.WinnerId, - principalTable: "Account", - principalColumn: "Id"); table.ForeignKey( name: "FK_Round_Room_RoomId", column: x => x.RoomId, @@ -105,6 +93,7 @@ protected override void Up(MigrationBuilder migrationBuilder) AccountId = table.Column(type: "TEXT", nullable: true), IsReady = table.Column(type: "INTEGER", nullable: false), Move = table.Column(type: "INTEGER", nullable: false), + IsWinner = table.Column(type: "INTEGER", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), RoundId = table.Column(type: "TEXT", nullable: true) }, @@ -143,22 +132,12 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Player", column: "RoundId"); - migrationBuilder.CreateIndex( - name: "IX_Round_LoserId", - table: "Round", - column: "LoserId"); - migrationBuilder.CreateIndex( name: "IX_Round_RoomId", table: "Round", column: "RoomId", unique: true); - migrationBuilder.CreateIndex( - name: "IX_Round_WinnerId", - table: "Round", - column: "WinnerId"); - migrationBuilder.CreateIndex( name: "IX_Statistics_AccountId", table: "Statistics", diff --git a/Server/Server.Host/Controllers/AccountController.cs b/Server/Server.Host/Controllers/AccountController.cs index fe2666e..657838c 100644 --- a/Server/Server.Host/Controllers/AccountController.cs +++ b/Server/Server.Host/Controllers/AccountController.cs @@ -14,9 +14,6 @@ namespace Server.Host.Controllers; -[ApiController] -[Consumes(MediaTypeNames.Application.Json)] -[Produces(MediaTypeNames.Application.Json)] public sealed class AccountController: ControllerBase { private readonly IAuthService _authService; @@ -25,7 +22,8 @@ public AccountController(IAuthService authService) { _authService = authService ?? throw new ArgumentNullException(nameof(authService)); } - + + [AllowAnonymous] [HttpPost(UrlTemplates.RegisterUrl)] [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] @@ -36,9 +34,10 @@ public async Task RegisterAsync(RegisterRequest registerRequest) return newAccount.Match( statusCode => Ok(statusCode.ToString()), - userException => BadRequest(userException)); + BadRequest); } + [AllowAnonymous] [HttpPost(UrlTemplates.LoginUrl)] [ProducesResponseType(typeof(AccountOutputModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(UserException),StatusCodes.Status400BadRequest)] @@ -48,11 +47,10 @@ public async Task LoginAsync(AccountDto accountDto) await _authService.LoginAsync(accountDto.Login, accountDto.Password); return newAccount.Match( - accountOutputModel => Ok(accountOutputModel), - userException => BadRequest(userException)); + Ok, + BadRequest); } - - [Authorize] + [HttpGet(UrlTemplates.LogoutUrl)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int) HttpStatusCode.BadRequest)] diff --git a/Server/Server.Host/Controllers/ControllerBase.cs b/Server/Server.Host/Controllers/ControllerBase.cs new file mode 100644 index 0000000..cfd38fe --- /dev/null +++ b/Server/Server.Host/Controllers/ControllerBase.cs @@ -0,0 +1,12 @@ +using System.Net.Mime; +using Microsoft.AspNetCore.Mvc; + +namespace Server.Host.Controllers; + +[ApiController] +[Consumes(MediaTypeNames.Application.Json)] +[Produces(MediaTypeNames.Application.Json)] +public class ControllerBase : Microsoft.AspNetCore.Mvc.ControllerBase +{ + protected string UserId => User.Identity?.Name ?? string.Empty; +} \ No newline at end of file diff --git a/Server/Server.Host/Controllers/LongPollingController.cs b/Server/Server.Host/Controllers/LongPollingController.cs index 517feb7..24e9393 100644 --- a/Server/Server.Host/Controllers/LongPollingController.cs +++ b/Server/Server.Host/Controllers/LongPollingController.cs @@ -1,7 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; @@ -9,8 +7,6 @@ namespace Server.Host.Controllers; -[ApiController] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class LongPollingController : ControllerBase { private readonly ILongPollingService _longPollingService; @@ -26,7 +22,7 @@ public Task CheckRoomUpdateTicksAsync(string roomId) { return _longPollingService.GetRoomUpdateTicksAsync(roomId); } - + [HttpGet(UrlTemplates.CheckRoundUpdateTicks)] [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] public Task CheckRoundUpdateTicksAsync(string roundId) diff --git a/Server/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs index c525c90..1c44282 100644 --- a/Server/Server.Host/Controllers/RoomController.cs +++ b/Server/Server.Host/Controllers/RoomController.cs @@ -1,8 +1,5 @@ using System; -using System.Net.Mime; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; @@ -11,10 +8,6 @@ namespace Server.Host.Controllers; -[ApiController] -[Consumes(MediaTypeNames.Application.Json)] -[Produces(MediaTypeNames.Application.Json)] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoomController: ControllerBase { private readonly IRoomService _roomService; @@ -24,8 +17,6 @@ public RoomController(IRoomService roomService) _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService)); } - private string UserId => User.Identity?.Name ?? string.Empty; - [HttpPost(UrlTemplates.CreateRoom)] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -37,8 +28,8 @@ public async Task CreateAsync( .CreateAsync(UserId, isPrivate, isTraining); return newRoom.Match( - roomModel => Ok(roomModel), - exception => BadRequest(exception)); + Ok, + BadRequest); } [HttpPost(UrlTemplates.JoinPublicRoom)] @@ -49,10 +40,10 @@ public async Task JoinPublicAsync() var result = await _roomService.JoinAsync(UserId); return result.Match( - roomModel => Ok(roomModel), - exception => BadRequest(exception)); + Ok, + BadRequest); } - + [HttpPost(UrlTemplates.JoinPrivateRoom)] [ProducesResponseType(typeof(RoomModel), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -61,8 +52,8 @@ public async Task JoinPrivateAsync(string roomCode) var result = await _roomService.JoinAsync(UserId, roomCode); return result.Match( - roomModel => Ok(roomModel), - exception => BadRequest(exception)); + Ok, + BadRequest); } [HttpPost(UrlTemplates.UpdateRoom)] @@ -88,6 +79,6 @@ public async Task DeleteAsync([FromQuery] string roomId) return deleteResponse.Match( _ => Ok(), - exception => BadRequest(exception)); + BadRequest); } } \ No newline at end of file diff --git a/Server/Server.Host/Controllers/RoundController.cs b/Server/Server.Host/Controllers/RoundController.cs index 3f97e6e..31e5a99 100644 --- a/Server/Server.Host/Controllers/RoundController.cs +++ b/Server/Server.Host/Controllers/RoundController.cs @@ -14,9 +14,6 @@ namespace Server.Host.Controllers; /// /// API Round Controller /// -[ApiController] -[Produces(MediaTypeNames.Application.Json)] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class RoundController: ControllerBase { private readonly IRoundService _roundService; @@ -26,8 +23,6 @@ public RoundController(IRoundService roundService) _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); } - private string UserId => User.Identity?.Name ?? string.Empty; - /// /// Creates round in room /// diff --git a/Server/Server.Host/Controllers/StatisticsController.cs b/Server/Server.Host/Controllers/StatisticsController.cs index ce236ed..2d95945 100644 --- a/Server/Server.Host/Controllers/StatisticsController.cs +++ b/Server/Server.Host/Controllers/StatisticsController.cs @@ -11,10 +11,6 @@ namespace Server.Host.Controllers; -[ApiController] -[Consumes(MediaTypeNames.Application.Json)] -[Produces(MediaTypeNames.Application.Json)] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public sealed class StatisticsController: ControllerBase { private readonly IStatisticsService _statisticsService; @@ -24,8 +20,6 @@ public StatisticsController(IStatisticsService statisticsService) _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); } - private string UserId => User.Identity?.Name ?? string.Empty; - [AllowAnonymous] [HttpGet(UrlTemplates.AllStatistics)] [ProducesResponseType(typeof(ShortStatisticsModel[]), StatusCodes.Status200OK)] @@ -34,7 +28,7 @@ public Task GetOverallStatistics() { return _statisticsService.GetAllAsync(); } - + [Authorize] [HttpGet(UrlTemplates.PersonalStatistics)] [ProducesResponseType(typeof(StatisticsModel), StatusCodes.Status200OK)] @@ -44,7 +38,7 @@ public async Task GetPersonalStatistics() var result = await _statisticsService.GetAsync(UserId); return result.Match( - statsModel => Ok(statsModel), - statsException => BadRequest(statsException)); + Ok, + BadRequest); } } \ No newline at end of file diff --git a/Server/Server.Host/Extensions/SwaggerExtension.cs b/Server/Server.Host/Extensions/SwaggerExtension.cs index 08f0218..008e5a5 100644 --- a/Server/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server/Server.Host/Extensions/SwaggerExtension.cs @@ -1,6 +1,9 @@ using System; +using System.IdentityModel.Tokens.Jwt; +using System.IO; using System.Reflection; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -22,16 +25,21 @@ public static IServiceCollection AddSwagger(this IServiceCollection services) services.AddSwaggerGen(options => { - // options.IncludeXmlComments($"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); - options.SwaggerDoc("v1", new OpenApiInfo + var title = AppDomain.CurrentDomain.FriendlyName; + var assemblyName = Assembly.GetExecutingAssembly().GetName(); + var version = assemblyName.Version?.ToString() ?? string.Empty; + var documentationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{assemblyName.Name}.xml"); + + options.IncludeXmlComments(documentationPath); + options.SwaggerDoc(version, new OpenApiInfo { - Title = "RPC Host", - Version = "v1" + Title = title, + Version = version }); - var jwtSecurityScheme = new OpenApiSecurityScheme + var jwtSecurityScheme = new OpenApiSecurityScheme { - BearerFormat = "JWT", + BearerFormat = JwtConstants.TokenType, Name = "JWT Authentication", In = ParameterLocation.Header, Type = SecuritySchemeType.Http, @@ -44,19 +52,33 @@ public static IServiceCollection AddSwagger(this IServiceCollection services) Type = ReferenceType.SecurityScheme } }; - + options.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement + + options.AddSecurityRequirement(new OpenApiSecurityRequirement { { jwtSecurityScheme, Array.Empty() - } - }); - + } + }); }); return services; } + + public static IApplicationBuilder UseSwaggerUI( + this IApplicationBuilder applicationBuilder) + { + ArgumentNullException.ThrowIfNull(applicationBuilder); + + applicationBuilder.UseSwaggerUI(swaggerUiOptions => + { + var title = AppDomain.CurrentDomain.FriendlyName; + var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; + swaggerUiOptions.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{title} v{version}"); + }); + + return applicationBuilder; + } } \ No newline at end of file diff --git a/Server/Server.Host/Server.Host.csproj b/Server/Server.Host/Server.Host.csproj index 632ac61..fa8cc31 100644 --- a/Server/Server.Host/Server.Host.csproj +++ b/Server/Server.Host/Server.Host.csproj @@ -1,6 +1,9 @@ + true + true + 0.0.2 net6 enable diff --git a/Server/Server.Host/Startup.cs b/Server/Server.Host/Startup.cs index 1c3a8a0..b91f74e 100644 --- a/Server/Server.Host/Startup.cs +++ b/Server/Server.Host/Startup.cs @@ -49,7 +49,7 @@ public static void Configure( { app.UseDeveloperExceptionPage(); app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server.Host v1")); + app.UseSwaggerUI(); } app.UseCors(builder => builder.AllowAnyOrigin() From 38df069ec721c08e5cb00d619e7061e89427d2e4 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Sun, 23 Oct 2022 23:12:45 +0300 Subject: [PATCH 53/56] More fixes --- Common/RockPaperScissors.Common/Enums/Move.cs | 12 ++ .../Enums/PlayerState.cs | 12 ++ .../Requests/MakeMoveRequest.cs | 10 ++ .../RockPaperScissors.Common/UrlTemplates.cs | 8 +- Server/Server.Authentication/AuthOptions.cs | 7 +- .../Exceptions/UserException.cs | 20 +-- .../Exceptions/UserExceptionsTemplates.cs | 2 - .../Extensions/AuthenticationExtension.cs | 3 +- Server/Server.Authentication/HashingBase64.cs | 5 +- .../Models/AccountOutputModel.cs | 2 +- Server/Server.Authentication/Models/Roles.cs | 2 +- .../Server.Authentication.csproj | 1 + .../Services/AttemptValidationService.cs | 15 +- .../Services/AuthService.cs | 37 ++-- .../Services/IAuthService.cs | 3 +- .../Exceptions/ExceptionTemplates.cs | 3 +- .../Extensions/ServiceCollectionExtensions.cs | 11 +- Server/Server.Bll/Models/AccountModel.cs | 3 +- Server/Server.Bll/Models/PlayerModel.cs | 5 +- Server/Server.Bll/Models/RoomModel.cs | 10 +- Server/Server.Bll/Models/RoundModel.cs | 6 +- Server/Server.Bll/Models/StatisticsModel.cs | 18 +- Server/Server.Bll/Server.Bll.csproj | 3 + .../Services/CleanerBackgroundService.cs | 60 +++++++ .../Services/CleanerHostedService.cs | 59 ------- .../Interfaces/ILongPollingService.cs | 5 +- .../Services/Interfaces/IRoomService.cs | 14 +- .../Services/Interfaces/IRoundService.cs | 11 +- .../Services/Interfaces/IStatisticsService.cs | 3 +- .../Server.Bll/Services/LongPollingService.cs | 7 +- Server/Server.Bll/Services/RoomService.cs | 57 ++++--- Server/Server.Bll/Services/RoundService.cs | 160 ++++++++---------- .../Server.Bll/Services/StatisticsService.cs | 3 - Server/Server.Data/Entities/Player.cs | 2 +- Server/Server.Data/Entities/PlayerState.cs | 12 ++ Server/Server.Data/Entities/Room.cs | 3 +- Server/Server.Data/Entities/Round.cs | 5 +- Server/Server.Data/Entities/Statistics.cs | 3 +- .../Extensions/SeedingExtension.cs | 3 - ...221023190847_InitialMigration.Designer.cs} | 9 +- ....cs => 20221023190847_InitialMigration.cs} | 3 +- .../Migrations/ServerContextModelSnapshot.cs | 28 +-- Server/Server.Data/Server.Data.csproj | 1 + .../Controllers/AccountController.cs | 6 +- .../Server.Host/Controllers/ControllerBase.cs | 3 + .../Controllers/LongPollingController.cs | 32 ---- .../Server.Host/Controllers/RoomController.cs | 36 ++-- .../Controllers/RoundController.cs | 55 ++---- .../Controllers/StatisticsController.cs | 7 +- .../Extensions/LoggingMiddleware.cs | 7 +- .../Extensions/SwaggerExtension.cs | 6 +- Server/Server.Host/Program.cs | 3 - Server/Server.Host/Server.Host.csproj | 3 +- Server/Server.Host/Startup.cs | 5 - 54 files changed, 364 insertions(+), 445 deletions(-) create mode 100644 Common/RockPaperScissors.Common/Enums/Move.cs create mode 100644 Common/RockPaperScissors.Common/Enums/PlayerState.cs create mode 100644 Common/RockPaperScissors.Common/Requests/MakeMoveRequest.cs create mode 100644 Server/Server.Bll/Services/CleanerBackgroundService.cs delete mode 100644 Server/Server.Bll/Services/CleanerHostedService.cs create mode 100644 Server/Server.Data/Entities/PlayerState.cs rename Server/Server.Data/Migrations/{20221023171541_InitialMigration.Designer.cs => 20221023190847_InitialMigration.Designer.cs} (97%) rename Server/Server.Data/Migrations/{20221023171541_InitialMigration.cs => 20221023190847_InitialMigration.cs} (97%) delete mode 100644 Server/Server.Host/Controllers/LongPollingController.cs diff --git a/Common/RockPaperScissors.Common/Enums/Move.cs b/Common/RockPaperScissors.Common/Enums/Move.cs new file mode 100644 index 0000000..bbb657a --- /dev/null +++ b/Common/RockPaperScissors.Common/Enums/Move.cs @@ -0,0 +1,12 @@ +namespace RockPaperScissors.Common.Enums; + +public enum Move +{ + None = 0, + + Rock = 1, + + Paper = 2, + + Scissors = 3, +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Enums/PlayerState.cs b/Common/RockPaperScissors.Common/Enums/PlayerState.cs new file mode 100644 index 0000000..8b3ca7a --- /dev/null +++ b/Common/RockPaperScissors.Common/Enums/PlayerState.cs @@ -0,0 +1,12 @@ +namespace RockPaperScissors.Common.Enums; + +public enum PlayerState +{ + None = 0, + + Lose = 1, + + Win = 2, + + Draw = 3, +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Requests/MakeMoveRequest.cs b/Common/RockPaperScissors.Common/Requests/MakeMoveRequest.cs new file mode 100644 index 0000000..a825a99 --- /dev/null +++ b/Common/RockPaperScissors.Common/Requests/MakeMoveRequest.cs @@ -0,0 +1,10 @@ +using RockPaperScissors.Common.Enums; + +namespace RockPaperScissors.Common.Requests; + +public sealed class MakeMoveRequest +{ + public string RoundId { get; init; } + + public Move Move { get; init; } +} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/UrlTemplates.cs b/Common/RockPaperScissors.Common/UrlTemplates.cs index 0d9fdbc..34458be 100644 --- a/Common/RockPaperScissors.Common/UrlTemplates.cs +++ b/Common/RockPaperScissors.Common/UrlTemplates.cs @@ -15,12 +15,14 @@ public static class UrlTemplates public const string CreateRoom = "api/room/create"; public const string JoinPublicRoom = "api/room/public/join"; public const string JoinPrivateRoom = "api/room/private/{roomCode}/join"; - public const string UpdateRoom = "api/room/update"; - public const string DeleteRoom = "api/room/delete"; + public const string UpdateRoom = "api/room/{roomId}/update"; + public const string DeleteRoom = "api/room/{roomId}/delete"; + public const string ChangeStatus = "api/room/{roomId}"; public const string CheckRoomUpdateTicks = "api/room/{roomId}/status"; // Round-related public const string CreateRound = "api/round/create"; - public const string UpdateRound = "api/round/update"; + public const string MakeMove = "api/round/{roundId}/move/{move}"; + public const string UpdateRound = "api/round/{roundId}/update"; public const string CheckRoundUpdateTicks = "api/round/{roundId}/status"; } \ No newline at end of file diff --git a/Server/Server.Authentication/AuthOptions.cs b/Server/Server.Authentication/AuthOptions.cs index 4123a39..eb59595 100644 --- a/Server/Server.Authentication/AuthOptions.cs +++ b/Server/Server.Authentication/AuthOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; using Microsoft.IdentityModel.Tokens; namespace Server.Authentication; @@ -14,8 +13,8 @@ public sealed class AuthOptions /// /// Token issuer (producer). /// - public string Issuer { get; init; } = "Rock Paper Scissors"; - + public string Issuer { get; init; } = AppDomain.CurrentDomain.FriendlyName; + /// /// Token audience (consumer). /// diff --git a/Server/Server.Authentication/Exceptions/UserException.cs b/Server/Server.Authentication/Exceptions/UserException.cs index fa3c546..d9cf49a 100644 --- a/Server/Server.Authentication/Exceptions/UserException.cs +++ b/Server/Server.Authentication/Exceptions/UserException.cs @@ -7,16 +7,6 @@ namespace Server.Authentication.Exceptions; /// public sealed class UserException { - /// - /// Gets response code. - /// - public int Code { get; } - - /// - /// Gets exception message. - /// - public string Message { get; } - /// /// Constructor. /// @@ -26,4 +16,14 @@ public UserException(string message) Code = StatusCodes.Status400BadRequest; Message = message; } + + /// + /// Gets response code. + /// + public int Code { get; } + + /// + /// Gets exception message. + /// + public string Message { get; } } \ No newline at end of file diff --git a/Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs b/Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs index 26b2525..9695652 100644 --- a/Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs +++ b/Server/Server.Authentication/Exceptions/UserExceptionsTemplates.cs @@ -1,5 +1,3 @@ -using System; - namespace Server.Authentication.Exceptions; /// diff --git a/Server/Server.Authentication/Extensions/AuthenticationExtension.cs b/Server/Server.Authentication/Extensions/AuthenticationExtension.cs index 786c880..d6b76de 100644 --- a/Server/Server.Authentication/Extensions/AuthenticationExtension.cs +++ b/Server/Server.Authentication/Extensions/AuthenticationExtension.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -29,7 +28,7 @@ public static IServiceCollection AddAuthentications(this IServiceCollection serv .BuildServiceProvider() .GetRequiredService>() .Value; - + options.RequireHttpsMetadata = jwtOptions.RequireHttps; options.TokenValidationParameters = new TokenValidationParameters { diff --git a/Server/Server.Authentication/HashingBase64.cs b/Server/Server.Authentication/HashingBase64.cs index e9bec0d..6fe2eb0 100644 --- a/Server/Server.Authentication/HashingBase64.cs +++ b/Server/Server.Authentication/HashingBase64.cs @@ -1,5 +1,3 @@ -using System; - namespace Server.Authentication; /// @@ -16,6 +14,7 @@ public static string DecodeBase64(this string encodedData) { var encodedDataAsBytes = Convert.FromBase64String(encodedData); + return System.Text.Encoding.ASCII.GetString(encodedDataAsBytes); } @@ -46,7 +45,7 @@ public static string EncodeBase64(this string initialData) public static bool IsHashEqual(this string base64Data, string initialData) { var baseDecoded = EncodeBase64(initialData); - + return base64Data.Equals(baseDecoded, StringComparison.OrdinalIgnoreCase); } } \ No newline at end of file diff --git a/Server/Server.Authentication/Models/AccountOutputModel.cs b/Server/Server.Authentication/Models/AccountOutputModel.cs index d579f0a..7ad8fd2 100644 --- a/Server/Server.Authentication/Models/AccountOutputModel.cs +++ b/Server/Server.Authentication/Models/AccountOutputModel.cs @@ -9,7 +9,7 @@ public sealed class AccountOutputModel /// Gets or sets user token (used in header). /// public string Token { get; init; } - + /// /// Gets or sets user login. /// diff --git a/Server/Server.Authentication/Models/Roles.cs b/Server/Server.Authentication/Models/Roles.cs index 95879b6..76bcbb4 100644 --- a/Server/Server.Authentication/Models/Roles.cs +++ b/Server/Server.Authentication/Models/Roles.cs @@ -9,7 +9,7 @@ public static class Roles /// Admin role. /// public const string Admin = "admin"; - + /// /// User role. /// diff --git a/Server/Server.Authentication/Server.Authentication.csproj b/Server/Server.Authentication/Server.Authentication.csproj index cc13671..967b793 100644 --- a/Server/Server.Authentication/Server.Authentication.csproj +++ b/Server/Server.Authentication/Server.Authentication.csproj @@ -3,6 +3,7 @@ true net6 + true enable diff --git a/Server/Server.Authentication/Services/AttemptValidationService.cs b/Server/Server.Authentication/Services/AttemptValidationService.cs index 082b598..960d209 100644 --- a/Server/Server.Authentication/Services/AttemptValidationService.cs +++ b/Server/Server.Authentication/Services/AttemptValidationService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; namespace Server.Authentication.Services; @@ -18,7 +17,7 @@ public static bool TryInsertFailAttempt(this string userId) { return false; } - + if (FailedAttempts.TryGetValue(userId, out var failedAttempts)) { if (failedAttempts >= 2) @@ -26,12 +25,12 @@ public static bool TryInsertFailAttempt(this string userId) // todo: in options CoolDownCollection.TryAdd(userId, DateTimeOffset.UtcNow.AddMinutes(2)); FailedAttempts.TryRemove(userId, out _); - + return true; } } FailedAttempts.AddOrUpdate(userId, 1, (_, i) => i + 1); - + return true; } @@ -41,7 +40,7 @@ public static bool TryInsertFailAttempt(this string userId) { return default; } - + return FailedAttempts.TryGetValue(userId, out var failedAttempts) ? failedAttempts : default; @@ -54,17 +53,17 @@ public static bool IsCoolDown(this string userId, out DateTimeOffset coolDownDat coolDownDate = default; return false; } - + if (!CoolDownCollection.TryGetValue(userId, out coolDownDate)) { return false; } - + if (coolDownDate >= DateTimeOffset.UtcNow) { return true; } - + CoolDownCollection.TryRemove(userId, out coolDownDate); return false; diff --git a/Server/Server.Authentication/Services/AuthService.cs b/Server/Server.Authentication/Services/AuthService.cs index 5a7ff91..7edb77f 100644 --- a/Server/Server.Authentication/Services/AuthService.cs +++ b/Server/Server.Authentication/Services/AuthService.cs @@ -1,8 +1,5 @@ -using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; @@ -52,17 +49,17 @@ public async Task> RegisterAsync( if (string.IsNullOrWhiteSpace(login)) { _logger.LogError("Login should not be 'empty'"); - + return new UserException(nameof(login).UserInvalidCredentials()); } - + if (string.IsNullOrEmpty(password)) { _logger.LogError("Password should not be 'empty'"); - + return new UserException(nameof(password).UserInvalidCredentials()); } - + var release = await Semaphore.WaitAsync(100); try @@ -70,21 +67,21 @@ public async Task> RegisterAsync( if (await _repository.Accounts.AnyAsync(account => account.Login.Equals(login.ToLower()))) { var exceptionMessage = login.UserAlreadyExists(); - + _logger.LogError("Error occured : {ExceptionMessage}", exceptionMessage); - + return new UserException(exceptionMessage); } var accountId = Guid.NewGuid().ToString(); - + var account = new Account { Id = accountId, Login = login, Password = password.EncodeBase64(), }; - + _repository.Accounts.Add(account); var accountStatistics = new Statistics @@ -92,7 +89,7 @@ public async Task> RegisterAsync( Id = accountId, AccountId = accountId }; - + _repository.StatisticsEnumerable.Add(accountStatistics); await _repository.SaveChangesAsync(); @@ -101,7 +98,7 @@ public async Task> RegisterAsync( catch { _logger.LogWarning("Unable to process account for {Login}", login); - + return new UserException(UserExceptionsTemplates.UnknownError); } finally @@ -112,27 +109,27 @@ public async Task> RegisterAsync( } } } - + /// public async Task> LoginAsync(string login, string password) { var userAccount = await _repository.Accounts.FirstOrDefaultAsync(account => account.Login.ToLower().Equals(login.ToLower())); string exceptionMessage; - + if (userAccount is null) { exceptionMessage = login.UserNotFound(); _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); - + return new UserException(exceptionMessage); } - + if (login.IsCoolDown(out var coolRequestDate)) { exceptionMessage = login.UserCoolDown(coolRequestDate); _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); - + return new UserException(exceptionMessage); } @@ -149,7 +146,7 @@ public async Task> LoginAsync(string lo exceptionMessage = login.UserInvalidCredentials(); _logger.LogWarning("Error occured: {ExceptionMessage}", exceptionMessage); - + return new UserException(exceptionMessage); } @@ -182,7 +179,7 @@ private static ClaimsIdentity GetClaimsIdentity(string userId) JwtBearerDefaults.AuthenticationScheme, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); - + return claimsIdentity; } } \ No newline at end of file diff --git a/Server/Server.Authentication/Services/IAuthService.cs b/Server/Server.Authentication/Services/IAuthService.cs index 4c67505..b48a630 100644 --- a/Server/Server.Authentication/Services/IAuthService.cs +++ b/Server/Server.Authentication/Services/IAuthService.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using OneOf; using Server.Authentication.Exceptions; @@ -24,7 +23,7 @@ public interface IAuthService /// UserException - If some case of error occured. (User exists, validation error, unknown error). /// Task> RegisterAsync(string login, string password); - + /// /// Signs in client by credentials and building JWT token. /// diff --git a/Server/Server.Bll/Exceptions/ExceptionTemplates.cs b/Server/Server.Bll/Exceptions/ExceptionTemplates.cs index 8ceb06a..be194a1 100644 --- a/Server/Server.Bll/Exceptions/ExceptionTemplates.cs +++ b/Server/Server.Bll/Exceptions/ExceptionTemplates.cs @@ -7,14 +7,13 @@ internal static class ExceptionTemplates internal const string NotAllowed = "You are not allowed to do this."; internal static string NotExists(string entity) => $"{entity} does not exist."; - // ROOM EXCEPTIONS internal const string TwinkRoom = "Failed to create one more game when you are sitting in another room."; internal const string RoomFull = "This room is full."; internal const string AlreadyInRoom = "You are already in room."; internal const string RoomNotFull = "Room is not full."; internal const string NoAvailableRooms = "Sorry, there are no public rooms available right now."; - + // ROUND EXCEPTIONS internal const string RoundAlreadyCreated = "Round is already creaded."; internal static string RoundNotFound(int roundId) => $"Round with id \"{roundId}\" is not found"; diff --git a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs index 9c39c05..e6c309c 100644 --- a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ -using System; -using Mapster; +using Mapster; using Microsoft.Extensions.DependencyInjection; using Server.Bll.Models; using Server.Bll.Services; @@ -18,14 +17,14 @@ public static IServiceCollection AddBusinessLogic(this IServiceCollection servic .NewConfig() .Map(shortStatisticsModel => shortStatisticsModel.Login, statistics => statistics.Account.Login) .Map(shortStatisticsModel => shortStatisticsModel.Score, statistics => statistics.Score); - + service .AddTransient() .AddTransient() - .AddHostedService(); - + .AddHostedService(); + service.AddHttpContextAccessor(); - + service .AddTransient() .AddTransient(); diff --git a/Server/Server.Bll/Models/AccountModel.cs b/Server/Server.Bll/Models/AccountModel.cs index bc20df6..d102163 100644 --- a/Server/Server.Bll/Models/AccountModel.cs +++ b/Server/Server.Bll/Models/AccountModel.cs @@ -1,7 +1,6 @@ - namespace Server.Bll.Models; public sealed class AccountModel { - public string Login { get; set; } + public string Login { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Models/PlayerModel.cs b/Server/Server.Bll/Models/PlayerModel.cs index 17dafe7..1586e6f 100644 --- a/Server/Server.Bll/Models/PlayerModel.cs +++ b/Server/Server.Bll/Models/PlayerModel.cs @@ -1,12 +1,11 @@ - -namespace Server.Bll.Models; +namespace Server.Bll.Models; public sealed class PlayerModel { public string Id { get; init; } public int Move { get; init; } - + public bool IsReady { get; init; } public bool IsWinner { get; init; } diff --git a/Server/Server.Bll/Models/RoomModel.cs b/Server/Server.Bll/Models/RoomModel.cs index 42d7c0b..b39b914 100644 --- a/Server/Server.Bll/Models/RoomModel.cs +++ b/Server/Server.Bll/Models/RoomModel.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Server.Bll.Models; +namespace Server.Bll.Models; public sealed class RoomModel { @@ -8,7 +6,7 @@ public sealed class RoomModel /// Id of the room. Consists of 5 randomized chars /// public string Id { get; init; } - + /// /// Special code to join a room /// @@ -33,12 +31,12 @@ public sealed class RoomModel /// Flag if room is full /// public bool IsFull { get; init; } - + /// /// Creation date. After 5 minutes of inactivity will be deleted /// public long CreationTimeTicks { get; init; } - + /// /// Last update time ticks. /// diff --git a/Server/Server.Bll/Models/RoundModel.cs b/Server/Server.Bll/Models/RoundModel.cs index 100e614..043df70 100644 --- a/Server/Server.Bll/Models/RoundModel.cs +++ b/Server/Server.Bll/Models/RoundModel.cs @@ -3,12 +3,12 @@ public sealed class RoundModel { public string Id { get; init; } - + public bool IsFinished { get; init; } public long StartTimeTicks { get; init; } - + public long FinishTimeTicks { get; init; } - + public long UpdateTicks { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Models/StatisticsModel.cs b/Server/Server.Bll/Models/StatisticsModel.cs index c3b6339..0ed68a3 100644 --- a/Server/Server.Bll/Models/StatisticsModel.cs +++ b/Server/Server.Bll/Models/StatisticsModel.cs @@ -5,46 +5,46 @@ public sealed class StatisticsModel /// /// Total amount of Wins /// - public int? Wins { get; set; } + public int? Wins { get; init; } /// /// Total amount of Loses /// - public int? Loss { get; set; } + public int? Loss { get; init; } /// /// Total amount of Draws. OBSOLETE /// - public int? Draws { get; set; } + public int? Draws { get; init; } /// /// Ratio Wins to Losses. Win/Loss * 100 /// - public double? WinLossRatio { get; set; } + public double? WinLossRatio { get; init; } /// /// Ratio for the last 7 days /// - public string TimeSpent { get; set; } + public string TimeSpent { get; init; } /// /// Times used rock /// - public int? UsedRock { get; set; } + public int? UsedRock { get; init; } /// /// Times used Paper /// - public int? UsedPaper { get; set; } + public int? UsedPaper { get; init; } /// /// Times used Scissors /// - public int? UsedScissors { get; set; } + public int? UsedScissors { get; init; } /// /// Total amount of Points. 1 win = 4 points. 1 lose = -2 points. /// - public int? Score { get; set; } + public int? Score { get; init; } } \ No newline at end of file diff --git a/Server/Server.Bll/Server.Bll.csproj b/Server/Server.Bll/Server.Bll.csproj index 2236c8c..4656682 100644 --- a/Server/Server.Bll/Server.Bll.csproj +++ b/Server/Server.Bll/Server.Bll.csproj @@ -1,7 +1,10 @@ + true + true net6 + true enable diff --git a/Server/Server.Bll/Services/CleanerBackgroundService.cs b/Server/Server.Bll/Services/CleanerBackgroundService.cs new file mode 100644 index 0000000..2d1f835 --- /dev/null +++ b/Server/Server.Bll/Services/CleanerBackgroundService.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Server.Bll.Services.Interfaces; + +namespace Server.Bll.Services; + +public sealed class CleanerBackgroundService : BackgroundService +{ + private readonly IServiceScopeFactory _serviceProvider; + private readonly ILogger _logger; + private readonly PeriodicTimer _periodicTimer; + // todo: options of max time + + public CleanerBackgroundService( + ILogger logger, + IServiceScopeFactory serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + _periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(10)); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Starting Background service"); + + while (await _periodicTimer.WaitForNextTickAsync(stoppingToken)) + { + await CleanJunk(_serviceProvider); + } + } + + private async Task CleanJunk(IServiceScopeFactory factory) + { + using var scope = factory.CreateScope(); + var roomService = scope.ServiceProvider.GetRequiredService(); + //todo: timespan to option. + var rooms = await roomService + .RemoveRangeAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); + + if (rooms > 0) + { + _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); + } + } + + public override void Dispose() + { + _periodicTimer.Dispose(); + base.Dispose(); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + _periodicTimer.Dispose(); + + return base.StopAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Server/Server.Bll/Services/CleanerHostedService.cs b/Server/Server.Bll/Services/CleanerHostedService.cs deleted file mode 100644 index 43bde08..0000000 --- a/Server/Server.Bll/Services/CleanerHostedService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Server.Bll.Services.Interfaces; - -namespace Server.Bll.Services; - -public sealed class CleanerHostedService : IHostedService -{ - private readonly IServiceScopeFactory _serviceProvider; - private readonly ILogger _logger; - private Timer? _timer; - - // todo: options of max time - - public CleanerHostedService( - ILogger logger, - IServiceScopeFactory serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("Starting Cleaning"); - - _timer = new Timer( - CleanJunk, - _serviceProvider, - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(10)); - - return Task.CompletedTask; - } - - private async void CleanJunk(object? state) - { - var factory = (IServiceScopeFactory) state!; - using var scope = factory.CreateScope(); - var roomService = scope.ServiceProvider.GetRequiredService(); - //todo: timespan to option. - var rooms = await roomService - .RemoveRangeAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); - - if(rooms > 0) - _logger.LogInformation("Cleaned {Room} entities", rooms.ToString()); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _timer?.Dispose(); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs index 45711bf..410ae29 100644 --- a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading.Tasks; - namespace Server.Bll.Services.Interfaces; public interface ILongPollingService @@ -9,7 +6,7 @@ public interface ILongPollingService Task CheckRoomState(string roomId); Task GetRoomUpdateTicksAsync(string roomId); - + [Obsolete] Task CheckRoundState(string roundId); diff --git a/Server/Server.Bll/Services/Interfaces/IRoomService.cs b/Server/Server.Bll/Services/Interfaces/IRoomService.cs index 1b48b75..f6b906f 100644 --- a/Server/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoomService.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using OneOf; using RockPaperScissors.Common; using Server.Bll.Models; @@ -9,14 +7,14 @@ namespace Server.Bll.Services.Interfaces; public interface IRoomService { Task> CreateAsync(string userId, bool isPrivate = false, bool isTraining = false); - + Task RemoveRangeAsync(TimeSpan roomOutDate, TimeSpan roundOutDate); - + Task> JoinAsync(string userId, string? roomCode = null); - + Task> GetAsync(string roomId); - - Task UpdateAsync(RoomModel room); - + + Task> ChangeReadyStatusAsync(string userId, string roomId, bool newStatus); + Task> DeleteAsync(string userId, string roomId); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/IRoundService.cs b/Server/Server.Bll/Services/Interfaces/IRoundService.cs index ca8b0ac..e711eab 100644 --- a/Server/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoundService.cs @@ -1,15 +1,10 @@ -using System.Threading.Tasks; -using Server.Bll.Models; +using RockPaperScissors.Common; using OneOf; -using RockPaperScissors.Common; +using RockPaperScissors.Common.Enums; namespace Server.Bll.Services.Interfaces; public interface IRoundService { - Task> CreateAsync(string userId, string roomId); - - Task MakeMoveAsync(); - - Task> UpdateAsync(string userId, RoundModel roundModel); + Task> MakeMoveAsync(string userId, string roundId, Move move); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs b/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs index 500b642..92a06a3 100644 --- a/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs +++ b/Server/Server.Bll/Services/Interfaces/IStatisticsService.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using OneOf; using RockPaperScissors.Common; using Server.Bll.Models; @@ -8,6 +7,6 @@ namespace Server.Bll.Services.Interfaces; public interface IStatisticsService { Task GetAllAsync(); - + Task> GetAsync(string userId); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/LongPollingService.cs b/Server/Server.Bll/Services/LongPollingService.cs index 4fab204..44ec265 100644 --- a/Server/Server.Bll/Services/LongPollingService.cs +++ b/Server/Server.Bll/Services/LongPollingService.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Server.Bll.Services.Interfaces; using Server.Data.Context; @@ -17,7 +16,7 @@ public LongPollingService(ServerContext serverContext) public Task CheckRoomState(string roomId) { return _serverContext.Rooms - .AnyAsync(room => room.Id.Equals(roomId)); + .AnyAsync(room => room.Id == roomId); } public async Task GetRoomUpdateTicksAsync(string roomId) @@ -36,7 +35,7 @@ public async Task GetRoomUpdateTicksAsync(string roomId) public async Task CheckRoundState(string roundId) { var round = await _serverContext.Rounds - .FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); + .FirstOrDefaultAsync(rounds => rounds.Id == roundId); return round?.IsFinished ?? false; } @@ -44,7 +43,7 @@ public async Task CheckRoundState(string roundId) public async Task GetRoundUpdateTicksAsync(string roundId) { var round = await _serverContext.Rounds - .FirstOrDefaultAsync(rounds => rounds.Id.Equals(roundId)); + .FirstOrDefaultAsync(rounds => rounds.Id== roundId); return round?.UpdateTicks ?? -1; } diff --git a/Server/Server.Bll/Services/RoomService.cs b/Server/Server.Bll/Services/RoomService.cs index ff4d507..0c008aa 100644 --- a/Server/Server.Bll/Services/RoomService.cs +++ b/Server/Server.Bll/Services/RoomService.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Mapster; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; @@ -65,15 +61,44 @@ public async Task> CreateAsync( { room.IsFull = true; room.Players.Add(SeedingExtension.BotPlayer); + room.Round = RoundService.Create(room); } - if (room.IsFull) + await _repository.SaveChangesAsync(); + + return room.Adapt(); + } + + public async Task> ChangeReadyStatusAsync(string userId, string roomId, bool newStatus) + { + var room = await _repository.Rooms + .Include(room => room.Players) + .FirstOrDefaultAsync(room => room.Id == roomId); + + if (room is null) { - room.Round = RoundService.Create(room); + return new CustomException($"Room with id {roomId} does not exist"); + } + + var currentPlayer = room.Players.FirstOrDefault(player => player.Id == userId); + if (currentPlayer is null) + { + return new CustomException($"You are not able to modify this room"); } + currentPlayer.IsReady = newStatus; + + _repository.Players.Update(currentPlayer); + await _repository.SaveChangesAsync(); + if (room.Players.All(player => player.IsReady)) + { + room.Round = RoundService.Create(room); + } + + await _repository.SaveChangesAsync(); + return room.Adapt(); } @@ -87,7 +112,7 @@ public async Task> JoinAsync(string userId, st } var room = oneOfRoom.AsT0; - + var newPlayer = new Player { Id = Guid.NewGuid().ToString(), @@ -95,20 +120,15 @@ public async Task> JoinAsync(string userId, st AccountId = userId, IsReady = false, }; - + room.Players.Add(newPlayer); room.UpdateTicks = DateTimeOffset.UtcNow.Ticks; room.IsFull = room.Players.Count is 2; - - if (room.IsFull) - { - room.Round = RoundService.Create(room); - } _repository.Rooms.Update(room); - + await _repository.SaveChangesAsync(); - + return room.Adapt(); } @@ -124,11 +144,6 @@ public async Task> GetAsync(string roomId) return room.Adapt(); } - public Task UpdateAsync(RoomModel room) - { - throw new NotImplementedException(); - } - public async Task> DeleteAsync(string userId, string roomId) { var room = await _repository.Rooms.FindAsync(roomId); @@ -217,7 +232,7 @@ private async Task> GetPublicAsync(string userId) if (room is null) { - return new CustomException("There is no available free rooms"); + return new CustomException("There are no available free rooms"); } return room; diff --git a/Server/Server.Bll/Services/RoundService.cs b/Server/Server.Bll/Services/RoundService.cs index 782ff7b..008eadd 100644 --- a/Server/Server.Bll/Services/RoundService.cs +++ b/Server/Server.Bll/Services/RoundService.cs @@ -1,15 +1,11 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Mapster; using Microsoft.EntityFrameworkCore; using RockPaperScissors.Common; -using Server.Bll.Models; using Server.Bll.Services.Interfaces; -using OneOf; -using Server.Bll.Exceptions; using Server.Data.Context; using Server.Data.Entities; +using Server.Data.Extensions; +using OneOf; +using RockPaperScissors.Common.Enums; namespace Server.Bll.Services; @@ -21,103 +17,86 @@ public RoundService(ServerContext serverContext) { _serverContext = serverContext ?? throw new ArgumentNullException(nameof(serverContext)); } - - public async Task> CreateAsync(string userId, string roomId) + + public async Task> MakeMoveAsync(string userId, string roundId, Move move) { - var playingRoom = await _serverContext.Rooms - .Include(rooms => rooms.Players) - .FirstOrDefaultAsync(room => room.Id.Equals(roomId)); - - if (playingRoom is null) - { - return new CustomException(ExceptionTemplates.NotExists(nameof(Room))); - } + var round = await _serverContext.Rounds + .Include(round => round.Players) + .Include(round => round.Room) + .FirstOrDefaultAsync(round => round.Id == roundId); - if (!playingRoom.IsFull) - { - return new CustomException(ExceptionTemplates.RoomNotFull); - } - - if (!playingRoom.Players.Any(player => player.AccountId.Equals(userId))) + if (round is null) { - return new CustomException(ExceptionTemplates.NotAllowed); + return new CustomException($"Unable to find round with id '{roundId}'"); } - var updateTime = DateTimeOffset.UtcNow.Ticks; - var newRound = new Round - { - Id = Guid.NewGuid().ToString(), - RoomId = roomId, - Room = playingRoom, - StartTimeTicks = updateTime, - UpdateTicks = updateTime, - IsFinished = false - }; + var updateTicks = DateTimeOffset.UtcNow.Ticks; + ProcessMoves(round, userId, move); - playingRoom.UpdateTicks = updateTime; - - var players = playingRoom.Players; + round.UpdateTicks = updateTicks; + round.Room.UpdateTicks = updateTicks; - newRound.Players = players; - - _serverContext.Rounds.Add(newRound); - _serverContext.Rooms.Update(playingRoom); + _serverContext.Update(round); await _serverContext.SaveChangesAsync(); - - return newRound.Adapt(); - } - [Obsolete(message: "Not used in new version. Please use UpdateRoundAsync")] - public Task MakeMoveAsync() - { - throw new NotImplementedException(); + return true; } - public Task> UpdateAsync(string userId, RoundModel roundModel) + private void ProcessMoves(Round round, string userId, Move move) { - throw new NotImplementedException(); - } + var players = round.Players; + var playingPlayer = players.First(player => player.Id == userId); + var otherPlayer = players.First(player => player.Id != userId); - public async Task> UpdateAsync(int userId, RoundModel roundModel) - { - throw new NotImplementedException(); - // var thisRound = await _serverContext - // .Rounds - // .Include(x => x.Player) - // .ThenInclude(x=>x.Room) - // .FirstOrDefaultAsync(x => x.Id == roundModel.Id); - // - // if(thisRound is null) - // { - // return new CustomException(ExceptionTemplates.RoundNotFound(roundModel.Id)); - // } - // - // if(thisRound.Player.FirstPlayerId != userId || thisRound.Player.SecondPlayerId != userId) - // { - // return new CustomException(ExceptionTemplates.NotAllowed); - // } - // - // var incomeRound = roundModel.Adapt(); - // thisRound.FirstPlayerMove = incomeRound.FirstPlayerMove; - // thisRound.SecondPlayerMove = incomeRound.SecondPlayerMove; - // thisRound.LastMoveTicks = incomeRound.LastMoveTicks; - // - // if (thisRound.FirstPlayerMove != 0 && thisRound.SecondPlayerMove != 0) - // { - // thisRound.IsFinished = true; - // thisRound.TimeFinishedTicks = DateTimeOffset.Now.Ticks; - // } - // - // if (!_serverContext.Entry(thisRound).Properties.Any(x => x.IsModified)) - // { - // return new CustomException(ExceptionTemplates.NotAllowed); - // } - // - // _serverContext.Update(thisRound); - // await _serverContext.SaveChangesAsync(); - // - // return thisRound.Adapt(); + if (otherPlayer.AccountId == SeedingExtension.BotId) + { + otherPlayer.Move = Random.Shared.Next(1, Enum.GetNames().Length); + } + + playingPlayer.Move = (int)move; + + if (otherPlayer.Move is (int)Move.None) + { + return; + } + + var playingPlayerMove = (Move)playingPlayer.Move; + var otherPlayerMove = (Move)otherPlayer.Move; + + playingPlayer.PlayerState = playingPlayerMove switch + { + Move.Paper => otherPlayerMove switch + { + Move.Rock => Data.Entities.PlayerState.Win, + Move.Scissors => Data.Entities.PlayerState.Lose, + Move.Paper => Data.Entities.PlayerState.Draw, + _ => Data.Entities.PlayerState.None, + }, + Move.Rock => otherPlayerMove switch + { + Move.Rock => Data.Entities.PlayerState.Draw, + Move.Scissors => Data.Entities.PlayerState.Win, + Move.Paper => Data.Entities.PlayerState.Lose, + _ => Data.Entities.PlayerState.None, + }, + Move.Scissors => otherPlayerMove switch + { + Move.Rock => Data.Entities.PlayerState.Lose, + Move.Scissors => Data.Entities.PlayerState.Draw, + Move.Paper => Data.Entities.PlayerState.Win, + _ => Data.Entities.PlayerState.None, + }, + _ => Data.Entities.PlayerState.None, + }; + + otherPlayer.PlayerState = playingPlayer.PlayerState switch + { + Data.Entities.PlayerState.Win => Data.Entities.PlayerState.Lose, + Data.Entities.PlayerState.Lose => Data.Entities.PlayerState.Win, + Data.Entities.PlayerState.Draw => Data.Entities.PlayerState.Draw, + _ => Data.Entities.PlayerState.None, + }; } public static Round Create(Room room) @@ -137,4 +116,5 @@ public static Round Create(Room room) return newRound; } + } \ No newline at end of file diff --git a/Server/Server.Bll/Services/StatisticsService.cs b/Server/Server.Bll/Services/StatisticsService.cs index efeba55..455ed35 100644 --- a/Server/Server.Bll/Services/StatisticsService.cs +++ b/Server/Server.Bll/Services/StatisticsService.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; -using System.Threading.Tasks; using Mapster; using Microsoft.EntityFrameworkCore; using RockPaperScissors.Common; diff --git a/Server/Server.Data/Entities/Player.cs b/Server/Server.Data/Entities/Player.cs index d548ccc..37911ae 100644 --- a/Server/Server.Data/Entities/Player.cs +++ b/Server/Server.Data/Entities/Player.cs @@ -19,5 +19,5 @@ public class Player public int Move { get; set; } - public bool IsWinner { get; set; } + public PlayerState PlayerState { get; set; } } \ No newline at end of file diff --git a/Server/Server.Data/Entities/PlayerState.cs b/Server/Server.Data/Entities/PlayerState.cs new file mode 100644 index 0000000..ed2d8a8 --- /dev/null +++ b/Server/Server.Data/Entities/PlayerState.cs @@ -0,0 +1,12 @@ +namespace Server.Data.Entities; + +public enum PlayerState +{ + None = 0, + + Lose = 1, + + Win = 2, + + Draw = 3, +} \ No newline at end of file diff --git a/Server/Server.Data/Entities/Room.cs b/Server/Server.Data/Entities/Room.cs index ecf5ade..9323e9d 100644 --- a/Server/Server.Data/Entities/Room.cs +++ b/Server/Server.Data/Entities/Room.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Data.Entities; diff --git a/Server/Server.Data/Entities/Round.cs b/Server/Server.Data/Entities/Round.cs index 1a47a9d..8a30d15 100644 --- a/Server/Server.Data/Entities/Round.cs +++ b/Server/Server.Data/Entities/Round.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Data.Entities; @@ -20,6 +19,8 @@ public class Round public bool IsFinished { get; init; } + public bool IsDraw { get; init; } + public long StartTimeTicks { get; set; } public long FinishTimeTicks { get; set; } diff --git a/Server/Server.Data/Entities/Statistics.cs b/Server/Server.Data/Entities/Statistics.cs index f9f54f2..a40b109 100644 --- a/Server/Server.Data/Entities/Statistics.cs +++ b/Server/Server.Data/Entities/Statistics.cs @@ -1,5 +1,4 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Server.Data.Entities; diff --git a/Server/Server.Data/Extensions/SeedingExtension.cs b/Server/Server.Data/Extensions/SeedingExtension.cs index bec0a3d..6756b96 100644 --- a/Server/Server.Data/Extensions/SeedingExtension.cs +++ b/Server/Server.Data/Extensions/SeedingExtension.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; using Server.Data.Context; using Server.Data.Entities; diff --git a/Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs b/Server/Server.Data/Migrations/20221023190847_InitialMigration.Designer.cs similarity index 97% rename from Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs rename to Server/Server.Data/Migrations/20221023190847_InitialMigration.Designer.cs index 0653c0e..5aa848d 100644 --- a/Server/Server.Data/Migrations/20221023171541_InitialMigration.Designer.cs +++ b/Server/Server.Data/Migrations/20221023190847_InitialMigration.Designer.cs @@ -11,7 +11,7 @@ namespace Server.Data.Migrations { [DbContext(typeof(ServerContext))] - [Migration("20221023171541_InitialMigration")] + [Migration("20221023190847_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -46,10 +46,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsReady") .HasColumnType("INTEGER"); - b.Property("IsWinner") + b.Property("Move") .HasColumnType("INTEGER"); - b.Property("Move") + b.Property("PlayerState") .HasColumnType("INTEGER"); b.Property("RoomId") @@ -102,6 +102,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("FinishTimeTicks") .HasColumnType("INTEGER"); + b.Property("IsDraw") + .HasColumnType("INTEGER"); + b.Property("IsFinished") .HasColumnType("INTEGER"); diff --git a/Server/Server.Data/Migrations/20221023171541_InitialMigration.cs b/Server/Server.Data/Migrations/20221023190847_InitialMigration.cs similarity index 97% rename from Server/Server.Data/Migrations/20221023171541_InitialMigration.cs rename to Server/Server.Data/Migrations/20221023190847_InitialMigration.cs index 9e348f7..15c3228 100644 --- a/Server/Server.Data/Migrations/20221023171541_InitialMigration.cs +++ b/Server/Server.Data/Migrations/20221023190847_InitialMigration.cs @@ -71,6 +71,7 @@ protected override void Up(MigrationBuilder migrationBuilder) Id = table.Column(type: "TEXT", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), IsFinished = table.Column(type: "INTEGER", nullable: false), + IsDraw = table.Column(type: "INTEGER", nullable: false), StartTimeTicks = table.Column(type: "INTEGER", nullable: false), FinishTimeTicks = table.Column(type: "INTEGER", nullable: false), UpdateTicks = table.Column(type: "INTEGER", nullable: false) @@ -93,7 +94,7 @@ protected override void Up(MigrationBuilder migrationBuilder) AccountId = table.Column(type: "TEXT", nullable: true), IsReady = table.Column(type: "INTEGER", nullable: false), Move = table.Column(type: "INTEGER", nullable: false), - IsWinner = table.Column(type: "INTEGER", nullable: false), + PlayerState = table.Column(type: "INTEGER", nullable: false), RoomId = table.Column(type: "TEXT", nullable: true), RoundId = table.Column(type: "TEXT", nullable: true) }, diff --git a/Server/Server.Data/Migrations/ServerContextModelSnapshot.cs b/Server/Server.Data/Migrations/ServerContextModelSnapshot.cs index 9bbc314..6220117 100644 --- a/Server/Server.Data/Migrations/ServerContextModelSnapshot.cs +++ b/Server/Server.Data/Migrations/ServerContextModelSnapshot.cs @@ -47,6 +47,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Move") .HasColumnType("INTEGER"); + b.Property("PlayerState") + .HasColumnType("INTEGER"); + b.Property("RoomId") .HasColumnType("TEXT"); @@ -97,11 +100,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("FinishTimeTicks") .HasColumnType("INTEGER"); - b.Property("IsFinished") + b.Property("IsDraw") .HasColumnType("INTEGER"); - b.Property("LoserId") - .HasColumnType("TEXT"); + b.Property("IsFinished") + .HasColumnType("INTEGER"); b.Property("RoomId") .HasColumnType("TEXT"); @@ -112,18 +115,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UpdateTicks") .HasColumnType("INTEGER"); - b.Property("WinnerId") - .HasColumnType("TEXT"); - b.HasKey("Id"); - b.HasIndex("LoserId"); - b.HasIndex("RoomId") .IsUnique(); - b.HasIndex("WinnerId"); - b.ToTable("Round"); }); @@ -189,23 +185,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Server.Data.Entities.Round", b => { - b.HasOne("Server.Data.Entities.Account", "Loser") - .WithMany() - .HasForeignKey("LoserId"); - b.HasOne("Server.Data.Entities.Room", "Room") .WithOne("Round") .HasForeignKey("Server.Data.Entities.Round", "RoomId"); - b.HasOne("Server.Data.Entities.Account", "Winner") - .WithMany() - .HasForeignKey("WinnerId"); - - b.Navigation("Loser"); - b.Navigation("Room"); - - b.Navigation("Winner"); }); modelBuilder.Entity("Server.Data.Entities.Statistics", b => diff --git a/Server/Server.Data/Server.Data.csproj b/Server/Server.Data/Server.Data.csproj index 338fef8..176511f 100644 --- a/Server/Server.Data/Server.Data.csproj +++ b/Server/Server.Data/Server.Data.csproj @@ -2,6 +2,7 @@ net6 + true diff --git a/Server/Server.Host/Controllers/AccountController.cs b/Server/Server.Host/Controllers/AccountController.cs index 657838c..6609bd7 100644 --- a/Server/Server.Host/Controllers/AccountController.cs +++ b/Server/Server.Host/Controllers/AccountController.cs @@ -1,9 +1,5 @@ -using System; -using System.Net; -using System.Net.Mime; -using System.Threading.Tasks; +using System.Net; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; using RockPaperScissors.Common.Models; diff --git a/Server/Server.Host/Controllers/ControllerBase.cs b/Server/Server.Host/Controllers/ControllerBase.cs index cfd38fe..6251b03 100644 --- a/Server/Server.Host/Controllers/ControllerBase.cs +++ b/Server/Server.Host/Controllers/ControllerBase.cs @@ -1,4 +1,6 @@ using System.Net.Mime; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Server.Host.Controllers; @@ -6,6 +8,7 @@ namespace Server.Host.Controllers; [ApiController] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class ControllerBase : Microsoft.AspNetCore.Mvc.ControllerBase { protected string UserId => User.Identity?.Name ?? string.Empty; diff --git a/Server/Server.Host/Controllers/LongPollingController.cs b/Server/Server.Host/Controllers/LongPollingController.cs deleted file mode 100644 index 24e9393..0000000 --- a/Server/Server.Host/Controllers/LongPollingController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using RockPaperScissors.Common; -using Server.Bll.Services.Interfaces; - -namespace Server.Host.Controllers; - -public sealed class LongPollingController : ControllerBase -{ - private readonly ILongPollingService _longPollingService; - - public LongPollingController(ILongPollingService longPollingService) - { - _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); - } - - [HttpGet(UrlTemplates.CheckRoomUpdateTicks)] - [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] - public Task CheckRoomUpdateTicksAsync(string roomId) - { - return _longPollingService.GetRoomUpdateTicksAsync(roomId); - } - - [HttpGet(UrlTemplates.CheckRoundUpdateTicks)] - [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] - public Task CheckRoundUpdateTicksAsync(string roundId) - { - return _longPollingService.GetRoundUpdateTicksAsync(roundId); - } -} \ No newline at end of file diff --git a/Server/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs index 1c44282..e703202 100644 --- a/Server/Server.Host/Controllers/RoomController.cs +++ b/Server/Server.Host/Controllers/RoomController.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; using Server.Bll.Models; using Server.Bll.Services.Interfaces; @@ -11,10 +8,12 @@ namespace Server.Host.Controllers; public sealed class RoomController: ControllerBase { private readonly IRoomService _roomService; + private readonly ILongPollingService _longPollingService; - public RoomController(IRoomService roomService) + public RoomController(IRoomService roomService, ILongPollingService longPollingService) { _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService)); + _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } [HttpPost(UrlTemplates.CreateRoom)] @@ -55,25 +54,11 @@ public async Task JoinPrivateAsync(string roomCode) Ok, BadRequest); } - - [HttpPost(UrlTemplates.UpdateRoom)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task UpdateAsync([FromBody] RoomModel roomModel) - { - var updateResponse = await _roomService.UpdateAsync(roomModel); - - return updateResponse switch - { - StatusCodes.Status200OK => Ok(), - _ => BadRequest() - }; - } - - [HttpDelete(UrlTemplates.DeleteRoom)] + + [HttpPost(UrlTemplates.DeleteRoom)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task DeleteAsync([FromQuery] string roomId) + public async Task DeleteAsync(string roomId) { var deleteResponse = await _roomService.DeleteAsync(UserId, roomId); @@ -81,4 +66,11 @@ public async Task DeleteAsync([FromQuery] string roomId) _ => Ok(), BadRequest); } + + [HttpGet(UrlTemplates.CheckRoomUpdateTicks)] + [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] + public Task CheckUpdateTicksAsync(string roomId) + { + return _longPollingService.GetRoomUpdateTicksAsync(roomId); + } } \ No newline at end of file diff --git a/Server/Server.Host/Controllers/RoundController.cs b/Server/Server.Host/Controllers/RoundController.cs index 31e5a99..dfdc929 100644 --- a/Server/Server.Host/Controllers/RoundController.cs +++ b/Server/Server.Host/Controllers/RoundController.cs @@ -1,12 +1,6 @@ -using System; -using System.Net; -using System.Net.Mime; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; -using Server.Bll.Models; +using RockPaperScissors.Common.Enums; using Server.Bll.Services.Interfaces; namespace Server.Host.Controllers; @@ -17,42 +11,27 @@ namespace Server.Host.Controllers; public sealed class RoundController: ControllerBase { private readonly IRoundService _roundService; + private readonly ILongPollingService _longPollingService; - public RoundController(IRoundService roundService) + public RoundController(IRoundService roundService, ILongPollingService longPollingService) { _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); + _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } - /// - /// Creates round in room - /// - /// id of the room - /// - [HttpPost(UrlTemplates.CreateRound)] - //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task CreateRound(int roomId) + [HttpGet(UrlTemplates.CheckRoundUpdateTicks)] + [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] + public Task CheckUpdateTicksAsync(string roundId) { - throw new NotImplementedException(); - // var result = await _roundService.CreateAsync(UserId, roomId); - // return result.Match( - // Ok, - // exception => BadRequest(exception)); - } - /// - /// Updates current room (Patches). - /// - /// This round model from FE or client. - /// - [HttpPatch(UrlTemplates.UpdateRound)] - //[ProducesResponseType(typeof(Round), (int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest)] - public async Task UpdateCurrentRound(RoundModel roundModel) + return _longPollingService.GetRoundUpdateTicksAsync(roundId); + } + + [HttpGet(UrlTemplates.MakeMove)] + [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] + public async Task MakeMoveAsync(string roundId, Move move) { - throw new NotImplementedException(); - // var updateResult = await _roundService.UpdateAsync(UserId, roundModel); - // return updateResult.Match( - // Ok, - // exception => BadRequest(exception)); + var makeMove = await _roundService.MakeMoveAsync(UserId, roundId, move); + + return makeMove.Match(_ => Ok(), BadRequest); } } \ No newline at end of file diff --git a/Server/Server.Host/Controllers/StatisticsController.cs b/Server/Server.Host/Controllers/StatisticsController.cs index 2d95945..fe6461f 100644 --- a/Server/Server.Host/Controllers/StatisticsController.cs +++ b/Server/Server.Host/Controllers/StatisticsController.cs @@ -1,9 +1,4 @@ -using System; -using System.Net.Mime; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RockPaperScissors.Common; using Server.Bll.Models; diff --git a/Server/Server.Host/Extensions/LoggingMiddleware.cs b/Server/Server.Host/Extensions/LoggingMiddleware.cs index f2e127f..f2dbdf7 100644 --- a/Server/Server.Host/Extensions/LoggingMiddleware.cs +++ b/Server/Server.Host/Extensions/LoggingMiddleware.cs @@ -1,10 +1,5 @@ -using System; -using System.IO; -using System.Net.Mime; +using System.Net.Mime; using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; namespace Server.Host.Extensions; diff --git a/Server/Server.Host/Extensions/SwaggerExtension.cs b/Server/Server.Host/Extensions/SwaggerExtension.cs index 008e5a5..08828ac 100644 --- a/Server/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server/Server.Host/Extensions/SwaggerExtension.cs @@ -1,10 +1,6 @@ -using System; -using System.IdentityModel.Tokens.Jwt; -using System.IO; +using System.IdentityModel.Tokens.Jwt; using System.Reflection; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; namespace Server.Host.Extensions; diff --git a/Server/Server.Host/Program.cs b/Server/Server.Host/Program.cs index e74c45c..3aa0f9c 100644 --- a/Server/Server.Host/Program.cs +++ b/Server/Server.Host/Program.cs @@ -1,6 +1,3 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Serilog; namespace Server.Host; diff --git a/Server/Server.Host/Server.Host.csproj b/Server/Server.Host/Server.Host.csproj index fa8cc31..8a33ddc 100644 --- a/Server/Server.Host/Server.Host.csproj +++ b/Server/Server.Host/Server.Host.csproj @@ -3,9 +3,10 @@ true true - 0.0.2 net6 + true enable + 0.0.2 diff --git a/Server/Server.Host/Startup.cs b/Server/Server.Host/Startup.cs index b91f74e..6e7a426 100644 --- a/Server/Server.Host/Startup.cs +++ b/Server/Server.Host/Startup.cs @@ -1,9 +1,4 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Server.Authentication.Extensions; using Server.Bll.Extensions; using Server.Data.Context; From 5dd9d4f37dcacdc65cfc6044fc5fcf9ebaa40534 Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Mon, 24 Oct 2022 00:27:22 +0300 Subject: [PATCH 54/56] More fixes and improvements --- Client/Client.MainMenu/Client.MainMenu.csproj | 4 -- .../Services/LongPollingService.cs | 11 ---- .../Extensions/TextWriteExtensions.cs | 43 +++++++++------ .../RockPaperScissors.Common/UrlTemplates.cs | 2 +- .../Interfaces/ILongPollingService.cs | 6 -- .../Services/Interfaces/IRoomService.cs | 2 +- Server/Server.Bll/Services/RoomService.cs | 13 +++-- Server/Server.Bll/Services/RoundService.cs | 55 ++++++++++++------- Server/Server.Data/Entities/Round.cs | 2 +- .../Server.Host/Controllers/RoomController.cs | 12 ++++ 10 files changed, 85 insertions(+), 65 deletions(-) delete mode 100644 Client/Client.MainMenu/Services/LongPollingService.cs diff --git a/Client/Client.MainMenu/Client.MainMenu.csproj b/Client/Client.MainMenu/Client.MainMenu.csproj index 09a039c..eb2460e 100644 --- a/Client/Client.MainMenu/Client.MainMenu.csproj +++ b/Client/Client.MainMenu/Client.MainMenu.csproj @@ -6,8 +6,4 @@ enable - - - - diff --git a/Client/Client.MainMenu/Services/LongPollingService.cs b/Client/Client.MainMenu/Services/LongPollingService.cs deleted file mode 100644 index 7af7a87..0000000 --- a/Client/Client.MainMenu/Services/LongPollingService.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Client.MainMenu.Services; - -public interface ILongPollingService -{ - //Todo : implement -} - -internal sealed class LongPollingService : ILongPollingService -{ - //Todo : implement -} \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs b/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs index f9ff470..9038de9 100644 --- a/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs +++ b/Common/RockPaperScissors.Common/Extensions/TextWriteExtensions.cs @@ -21,20 +21,24 @@ public static string BuildString(this string msg, StringDestination destination, while (true) { var passwordNotConfirmed = true; + Print( destination is StringDestination.PassportType or StringDestination.Email ? $"What is your {msg}?" - : $"Try to come up with {msg}?", ConsoleColor.Yellow); + : $"Try to come up with {msg}", ConsoleColor.Yellow); + Console.Write($"{msg}--> "); - + output = Console.ReadLine() ?.Trim() - ?.Replace(" ", ""); + .Replace(" ", "") ?? string.Empty; + if (string.IsNullOrEmpty(output)) { Print("Wrong data!", ConsoleColor.Red); continue; } + switch (destination) { case StringDestination.Password when output.Length < 6: @@ -45,10 +49,13 @@ destination is StringDestination.PassportType or StringDestination.Email continue; } - if (destination == StringDestination.Password) + if (destination is StringDestination.Password) { if (isNeedConfirmation) + { break; + } + Print("You need to confirm password!", ConsoleColor.Yellow); do { @@ -56,19 +63,23 @@ destination is StringDestination.PassportType or StringDestination.Email var confirmationPassword = Console.ReadLine() ?.Trim() .Replace(" ", ""); + if (string.IsNullOrEmpty(output)) { Print("Wrong data!", ConsoleColor.Red); continue; } - if (output == confirmationPassword) + + if (output != confirmationPassword) { - Print("Password confirmed", ConsoleColor.Green); - passwordNotConfirmed = false; + Print("Passwords don't match!",ConsoleColor.Red); + continue; } - else - Print("Passwords dont match!",ConsoleColor.Red); - } while (passwordNotConfirmed); + + Print("Password confirmed", ConsoleColor.Green); + passwordNotConfirmed = false; + } + while (passwordNotConfirmed); } if (destination is StringDestination.PassportType && ContainsDigits(output)) { @@ -77,21 +88,21 @@ destination is StringDestination.PassportType or StringDestination.Email } break; } - + return output; } - + public static bool ContainsDigits(this string str) { - var res = str.Any(character => char.IsDigit(character)); - + var res = str.Any(char.IsDigit); + return res; } - + public static bool IsEmailValid(this string email) { var match = EmailRegex.Match(email); - + return match.Success; } } \ No newline at end of file diff --git a/Common/RockPaperScissors.Common/UrlTemplates.cs b/Common/RockPaperScissors.Common/UrlTemplates.cs index 34458be..b278632 100644 --- a/Common/RockPaperScissors.Common/UrlTemplates.cs +++ b/Common/RockPaperScissors.Common/UrlTemplates.cs @@ -17,7 +17,7 @@ public static class UrlTemplates public const string JoinPrivateRoom = "api/room/private/{roomCode}/join"; public const string UpdateRoom = "api/room/{roomId}/update"; public const string DeleteRoom = "api/room/{roomId}/delete"; - public const string ChangeStatus = "api/room/{roomId}"; + public const string ChangeStatus = "api/room/{roomId}/playerstatus"; public const string CheckRoomUpdateTicks = "api/room/{roomId}/status"; // Round-related diff --git a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs index 410ae29..5bd62cb 100644 --- a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs @@ -2,13 +2,7 @@ namespace Server.Bll.Services.Interfaces; public interface ILongPollingService { - [Obsolete] - Task CheckRoomState(string roomId); - Task GetRoomUpdateTicksAsync(string roomId); - [Obsolete] - Task CheckRoundState(string roundId); - Task GetRoundUpdateTicksAsync(string roundId); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/IRoomService.cs b/Server/Server.Bll/Services/Interfaces/IRoomService.cs index f6b906f..262bb41 100644 --- a/Server/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoomService.cs @@ -14,7 +14,7 @@ public interface IRoomService Task> GetAsync(string roomId); - Task> ChangeReadyStatusAsync(string userId, string roomId, bool newStatus); + Task> ChangePlayerStatusAsync(string userId, string roomId, bool newStatus); Task> DeleteAsync(string userId, string roomId); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/RoomService.cs b/Server/Server.Bll/Services/RoomService.cs index 0c008aa..24dbe87 100644 --- a/Server/Server.Bll/Services/RoomService.cs +++ b/Server/Server.Bll/Services/RoomService.cs @@ -59,6 +59,7 @@ public async Task> CreateAsync( if (isTraining) { + room.IsPrivate = true; room.IsFull = true; room.Players.Add(SeedingExtension.BotPlayer); room.Round = RoundService.Create(room); @@ -69,7 +70,7 @@ public async Task> CreateAsync( return room.Adapt(); } - public async Task> ChangeReadyStatusAsync(string userId, string roomId, bool newStatus) + public async Task> ChangePlayerStatusAsync(string userId, string roomId, bool newStatus) { var room = await _repository.Rooms .Include(room => room.Players) @@ -77,24 +78,24 @@ public async Task> ChangeReadyStatusAsync(stri if (room is null) { - return new CustomException($"Room with id {roomId} does not exist"); + return new CustomException($"Room with id '{roomId}' does not exist"); } - var currentPlayer = room.Players.FirstOrDefault(player => player.Id == userId); + var currentPlayer = room.Players.FirstOrDefault(player => player.AccountId == userId); if (currentPlayer is null) { - return new CustomException($"You are not able to modify this room"); + return new CustomException("You are not able to modify this room"); } currentPlayer.IsReady = newStatus; _repository.Players.Update(currentPlayer); - - await _repository.SaveChangesAsync(); + _repository.Rooms.Update(room); if (room.Players.All(player => player.IsReady)) { room.Round = RoundService.Create(room); + await _repository.SaveChangesAsync(); } await _repository.SaveChangesAsync(); diff --git a/Server/Server.Bll/Services/RoundService.cs b/Server/Server.Bll/Services/RoundService.cs index 008eadd..d11931c 100644 --- a/Server/Server.Bll/Services/RoundService.cs +++ b/Server/Server.Bll/Services/RoundService.cs @@ -6,6 +6,7 @@ using Server.Data.Extensions; using OneOf; using RockPaperScissors.Common.Enums; +using PlayerState = Server.Data.Entities.PlayerState; namespace Server.Bll.Services; @@ -30,6 +31,11 @@ public async Task> MakeMoveAsync(string userId, str return new CustomException($"Unable to find round with id '{roundId}'"); } + if (round.IsFinished) + { + return new CustomException($"Round has been finished."); + } + var updateTicks = DateTimeOffset.UtcNow.Ticks; ProcessMoves(round, userId, move); @@ -46,8 +52,14 @@ public async Task> MakeMoveAsync(string userId, str private void ProcessMoves(Round round, string userId, Move move) { var players = round.Players; - var playingPlayer = players.First(player => player.Id == userId); - var otherPlayer = players.First(player => player.Id != userId); + var playingPlayer = players.FirstOrDefault(player => player.AccountId == userId); + + if (playingPlayer is null) + { + return; + } + + var otherPlayer = players.First(player => player.AccountId != userId); if (otherPlayer.AccountId == SeedingExtension.BotId) { @@ -68,35 +80,40 @@ private void ProcessMoves(Round round, string userId, Move move) { Move.Paper => otherPlayerMove switch { - Move.Rock => Data.Entities.PlayerState.Win, - Move.Scissors => Data.Entities.PlayerState.Lose, - Move.Paper => Data.Entities.PlayerState.Draw, - _ => Data.Entities.PlayerState.None, + Move.Rock => PlayerState.Win, + Move.Scissors => PlayerState.Lose, + Move.Paper => PlayerState.Draw, + _ => PlayerState.None, }, Move.Rock => otherPlayerMove switch { - Move.Rock => Data.Entities.PlayerState.Draw, - Move.Scissors => Data.Entities.PlayerState.Win, - Move.Paper => Data.Entities.PlayerState.Lose, - _ => Data.Entities.PlayerState.None, + Move.Rock => PlayerState.Draw, + Move.Scissors => PlayerState.Win, + Move.Paper => PlayerState.Lose, + _ => PlayerState.None, }, Move.Scissors => otherPlayerMove switch { - Move.Rock => Data.Entities.PlayerState.Lose, - Move.Scissors => Data.Entities.PlayerState.Draw, - Move.Paper => Data.Entities.PlayerState.Win, - _ => Data.Entities.PlayerState.None, + Move.Rock => PlayerState.Lose, + Move.Scissors => PlayerState.Draw, + Move.Paper => PlayerState.Win, + _ => PlayerState.None, }, - _ => Data.Entities.PlayerState.None, + _ => PlayerState.None, }; otherPlayer.PlayerState = playingPlayer.PlayerState switch { - Data.Entities.PlayerState.Win => Data.Entities.PlayerState.Lose, - Data.Entities.PlayerState.Lose => Data.Entities.PlayerState.Win, - Data.Entities.PlayerState.Draw => Data.Entities.PlayerState.Draw, - _ => Data.Entities.PlayerState.None, + PlayerState.Win => PlayerState.Lose, + PlayerState.Lose => PlayerState.Win, + PlayerState.Draw => PlayerState.Draw, + _ => PlayerState.None, }; + + if (playingPlayer.PlayerState is not PlayerState.None && otherPlayer.PlayerState is not PlayerState.None) + { + round.IsFinished = true; + } } public static Round Create(Room room) diff --git a/Server/Server.Data/Entities/Round.cs b/Server/Server.Data/Entities/Round.cs index 8a30d15..495f2ea 100644 --- a/Server/Server.Data/Entities/Round.cs +++ b/Server/Server.Data/Entities/Round.cs @@ -17,7 +17,7 @@ public class Round public virtual ICollection Players { get; set; } - public bool IsFinished { get; init; } + public bool IsFinished { get; set; } public bool IsDraw { get; init; } diff --git a/Server/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs index e703202..62c9af4 100644 --- a/Server/Server.Host/Controllers/RoomController.cs +++ b/Server/Server.Host/Controllers/RoomController.cs @@ -66,6 +66,18 @@ public async Task DeleteAsync(string roomId) _ => Ok(), BadRequest); } + + [HttpPost(UrlTemplates.ChangeStatus)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ChangePlayerStatusAsync(string roomId, [FromQuery] bool newStatus) + { + var changePlayerStatus = await _roomService.ChangePlayerStatusAsync(UserId, roomId, newStatus); + + return changePlayerStatus.Match( + Ok, + BadRequest); + } [HttpGet(UrlTemplates.CheckRoomUpdateTicks)] [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] From e5f20ea31accf56d949c9ee1b8b9a13c72503f3e Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Mon, 24 Oct 2022 00:55:17 +0300 Subject: [PATCH 55/56] Added IOptions and removed ILongPollingService.cs --- .../Extensions/ServiceCollectionExtensions.cs | 1 - Server/Server.Bll/Options/CleanerOptions.cs | 12 +++++ .../Services/CleanerBackgroundService.cs | 20 +++++--- .../Interfaces/ILongPollingService.cs | 8 --- .../Services/Interfaces/IRoomService.cs | 2 + .../Services/Interfaces/IRoundService.cs | 2 + .../Server.Bll/Services/LongPollingService.cs | 50 ------------------- Server/Server.Bll/Services/RoomService.cs | 15 +++++- Server/Server.Bll/Services/RoundService.cs | 8 +++ .../Server.Host/Controllers/RoomController.cs | 6 +-- .../Controllers/RoundController.cs | 6 +-- Server/Server.Host/Program.cs | 12 +++-- Server/Server.Host/Startup.cs | 21 ++++---- Server/Server.Host/appsettings.json | 5 ++ 14 files changed, 80 insertions(+), 88 deletions(-) create mode 100644 Server/Server.Bll/Options/CleanerOptions.cs delete mode 100644 Server/Server.Bll/Services/Interfaces/ILongPollingService.cs delete mode 100644 Server/Server.Bll/Services/LongPollingService.cs diff --git a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs index e6c309c..5edff9d 100644 --- a/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs +++ b/Server/Server.Bll/Extensions/ServiceCollectionExtensions.cs @@ -20,7 +20,6 @@ public static IServiceCollection AddBusinessLogic(this IServiceCollection servic service .AddTransient() - .AddTransient() .AddHostedService(); service.AddHttpContextAccessor(); diff --git a/Server/Server.Bll/Options/CleanerOptions.cs b/Server/Server.Bll/Options/CleanerOptions.cs new file mode 100644 index 0000000..e43abfd --- /dev/null +++ b/Server/Server.Bll/Options/CleanerOptions.cs @@ -0,0 +1,12 @@ +namespace Server.Bll.Options; + +public sealed class CleanerOptions +{ + public const string Section = "Cleaning"; + + public TimeSpan CleanPeriod { get; init; } + + public TimeSpan RoomOutDateTime { get; init; } + + public TimeSpan RoundOutDateTime { get; init; } +} \ No newline at end of file diff --git a/Server/Server.Bll/Services/CleanerBackgroundService.cs b/Server/Server.Bll/Services/CleanerBackgroundService.cs index 2d1f835..c8792d8 100644 --- a/Server/Server.Bll/Services/CleanerBackgroundService.cs +++ b/Server/Server.Bll/Services/CleanerBackgroundService.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Server.Bll.Options; using Server.Bll.Services.Interfaces; namespace Server.Bll.Services; @@ -10,15 +12,17 @@ public sealed class CleanerBackgroundService : BackgroundService private readonly IServiceScopeFactory _serviceProvider; private readonly ILogger _logger; private readonly PeriodicTimer _periodicTimer; - // todo: options of max time + private readonly CleanerOptions _cleanerOptions; public CleanerBackgroundService( ILogger logger, - IServiceScopeFactory serviceProvider) + IServiceScopeFactory serviceProvider, + IOptions cleanerOptions) { - _logger = logger; - _serviceProvider = serviceProvider; - _periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(10)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _cleanerOptions = cleanerOptions?.Value ?? throw new ArgumentNullException(nameof(cleanerOptions)); + _periodicTimer = new PeriodicTimer(_cleanerOptions.CleanPeriod); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -35,9 +39,8 @@ private async Task CleanJunk(IServiceScopeFactory factory) { using var scope = factory.CreateScope(); var roomService = scope.ServiceProvider.GetRequiredService(); - //todo: timespan to option. var rooms = await roomService - .RemoveRangeAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(20)); + .RemoveRangeAsync(_cleanerOptions.RoomOutDateTime, _cleanerOptions.RoundOutDateTime); if (rooms > 0) { @@ -48,13 +51,14 @@ private async Task CleanJunk(IServiceScopeFactory factory) public override void Dispose() { _periodicTimer.Dispose(); + base.Dispose(); } public override Task StopAsync(CancellationToken cancellationToken) { _periodicTimer.Dispose(); - + return base.StopAsync(cancellationToken); } } \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs b/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs deleted file mode 100644 index 5bd62cb..0000000 --- a/Server/Server.Bll/Services/Interfaces/ILongPollingService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Server.Bll.Services.Interfaces; - -public interface ILongPollingService -{ - Task GetRoomUpdateTicksAsync(string roomId); - - Task GetRoundUpdateTicksAsync(string roundId); -} \ No newline at end of file diff --git a/Server/Server.Bll/Services/Interfaces/IRoomService.cs b/Server/Server.Bll/Services/Interfaces/IRoomService.cs index 262bb41..d3b0f46 100644 --- a/Server/Server.Bll/Services/Interfaces/IRoomService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoomService.cs @@ -14,6 +14,8 @@ public interface IRoomService Task> GetAsync(string roomId); + Task GetUpdateTicksAsync(string roomId); + Task> ChangePlayerStatusAsync(string userId, string roomId, bool newStatus); Task> DeleteAsync(string userId, string roomId); diff --git a/Server/Server.Bll/Services/Interfaces/IRoundService.cs b/Server/Server.Bll/Services/Interfaces/IRoundService.cs index e711eab..baa9a19 100644 --- a/Server/Server.Bll/Services/Interfaces/IRoundService.cs +++ b/Server/Server.Bll/Services/Interfaces/IRoundService.cs @@ -7,4 +7,6 @@ namespace Server.Bll.Services.Interfaces; public interface IRoundService { Task> MakeMoveAsync(string userId, string roundId, Move move); + + Task GetUpdateTicksAsync(string roundId); } \ No newline at end of file diff --git a/Server/Server.Bll/Services/LongPollingService.cs b/Server/Server.Bll/Services/LongPollingService.cs deleted file mode 100644 index 44ec265..0000000 --- a/Server/Server.Bll/Services/LongPollingService.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Server.Bll.Services.Interfaces; -using Server.Data.Context; - -namespace Server.Bll.Services; - -internal sealed class LongPollingService : ILongPollingService -{ - private readonly ServerContext _serverContext; - - public LongPollingService(ServerContext serverContext) - { - _serverContext = serverContext; - } - - public Task CheckRoomState(string roomId) - { - return _serverContext.Rooms - .AnyAsync(room => room.Id == roomId); - } - - public async Task GetRoomUpdateTicksAsync(string roomId) - { - var room = await _serverContext.Rooms - .FindAsync(roomId); - - if (room is null) - { - return -1; - } - - return room.UpdateTicks; - } - - public async Task CheckRoundState(string roundId) - { - var round = await _serverContext.Rounds - .FirstOrDefaultAsync(rounds => rounds.Id == roundId); - - return round?.IsFinished ?? false; - } - - public async Task GetRoundUpdateTicksAsync(string roundId) - { - var round = await _serverContext.Rounds - .FirstOrDefaultAsync(rounds => rounds.Id== roundId); - - return round?.UpdateTicks ?? -1; - } -} \ No newline at end of file diff --git a/Server/Server.Bll/Services/RoomService.cs b/Server/Server.Bll/Services/RoomService.cs index 24dbe87..012ffff 100644 --- a/Server/Server.Bll/Services/RoomService.cs +++ b/Server/Server.Bll/Services/RoomService.cs @@ -102,7 +102,20 @@ public async Task> ChangePlayerStatusAsync(str return room.Adapt(); } - + + public async Task GetUpdateTicksAsync(string roomId) + { + var room = await _repository.Rooms + .FindAsync(roomId); + + if (room is null) + { + return -1; + } + + return room.UpdateTicks; + } + public async Task> JoinAsync(string userId, string? roomCode) { var oneOfRoom = string.IsNullOrEmpty(roomCode) ? await GetPublicAsync(userId) : await GetPrivateAsync(userId, roomCode); diff --git a/Server/Server.Bll/Services/RoundService.cs b/Server/Server.Bll/Services/RoundService.cs index d11931c..398d519 100644 --- a/Server/Server.Bll/Services/RoundService.cs +++ b/Server/Server.Bll/Services/RoundService.cs @@ -49,6 +49,14 @@ public async Task> MakeMoveAsync(string userId, str return true; } + public async Task GetUpdateTicksAsync(string roundId) + { + var round = await _serverContext.Rounds + .FirstOrDefaultAsync(rounds => rounds.Id== roundId); + + return round?.UpdateTicks ?? -1; + } + private void ProcessMoves(Round round, string userId, Move move) { var players = round.Players; diff --git a/Server/Server.Host/Controllers/RoomController.cs b/Server/Server.Host/Controllers/RoomController.cs index 62c9af4..a8d5102 100644 --- a/Server/Server.Host/Controllers/RoomController.cs +++ b/Server/Server.Host/Controllers/RoomController.cs @@ -8,12 +8,10 @@ namespace Server.Host.Controllers; public sealed class RoomController: ControllerBase { private readonly IRoomService _roomService; - private readonly ILongPollingService _longPollingService; - public RoomController(IRoomService roomService, ILongPollingService longPollingService) + public RoomController(IRoomService roomService) { _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService)); - _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } [HttpPost(UrlTemplates.CreateRoom)] @@ -83,6 +81,6 @@ public async Task ChangePlayerStatusAsync(string roomId, [FromQue [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] public Task CheckUpdateTicksAsync(string roomId) { - return _longPollingService.GetRoomUpdateTicksAsync(roomId); + return _roomService.GetUpdateTicksAsync(roomId); } } \ No newline at end of file diff --git a/Server/Server.Host/Controllers/RoundController.cs b/Server/Server.Host/Controllers/RoundController.cs index dfdc929..75cb410 100644 --- a/Server/Server.Host/Controllers/RoundController.cs +++ b/Server/Server.Host/Controllers/RoundController.cs @@ -11,19 +11,17 @@ namespace Server.Host.Controllers; public sealed class RoundController: ControllerBase { private readonly IRoundService _roundService; - private readonly ILongPollingService _longPollingService; - public RoundController(IRoundService roundService, ILongPollingService longPollingService) + public RoundController(IRoundService roundService) { _roundService = roundService ?? throw new ArgumentNullException(nameof(roundService)); - _longPollingService = longPollingService ?? throw new ArgumentNullException(nameof(longPollingService)); } [HttpGet(UrlTemplates.CheckRoundUpdateTicks)] [ProducesResponseType(typeof(long), StatusCodes.Status200OK)] public Task CheckUpdateTicksAsync(string roundId) { - return _longPollingService.GetRoundUpdateTicksAsync(roundId); + return _roundService.GetUpdateTicksAsync(roundId); } [HttpGet(UrlTemplates.MakeMove)] diff --git a/Server/Server.Host/Program.cs b/Server/Server.Host/Program.cs index 3aa0f9c..b6aab50 100644 --- a/Server/Server.Host/Program.cs +++ b/Server/Server.Host/Program.cs @@ -9,8 +9,9 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureLogging(loggingBuilder => { loggingBuilder.ClearProviders(); @@ -19,5 +20,10 @@ private static IHostBuilder CreateHostBuilder(string[] args) => .WriteTo.Console() .CreateLogger()); }) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } + } \ No newline at end of file diff --git a/Server/Server.Host/Startup.cs b/Server/Server.Host/Startup.cs index 6e7a426..f745838 100644 --- a/Server/Server.Host/Startup.cs +++ b/Server/Server.Host/Startup.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Server.Authentication.Extensions; using Server.Bll.Extensions; +using Server.Bll.Options; using Server.Data.Context; using Server.Data.Extensions; using Server.Host.Extensions; @@ -15,23 +16,26 @@ public Startup(IConfiguration configuration) } private IConfiguration Configuration { get; } - + public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks(); - + + services + .Configure(Configuration.GetRequiredSection(CleanerOptions.Section)); + services .AddDatabase(Configuration) .AddSwagger() .AddAuthentications(); - + services.AddBusinessLogic(); - + services.AddControllers(); - + services.AddCors(); } - + public static void Configure( IApplicationBuilder app, IWebHostEnvironment env, @@ -39,14 +43,14 @@ public static void Configure( { serverContext.Database.Migrate(); serverContext?.EnsureBotCreated(); - + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); } - + app.UseCors(builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() @@ -57,7 +61,6 @@ public static void Configure( app.UseAuthorization(); - app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); diff --git a/Server/Server.Host/appsettings.json b/Server/Server.Host/appsettings.json index e163015..b1a240e 100644 --- a/Server/Server.Host/appsettings.json +++ b/Server/Server.Host/appsettings.json @@ -9,5 +9,10 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "Cleaning": { + "CleanPeriod": "00:00:10", + "RoomOutDateTime": "00:05:00", + "RoundOutDateTime": "00:00:30" + }, "AllowedHosts": "*" } From 5222d3ab1ca6df59b242793a817646176a193e3d Mon Sep 17 00:00:00 2001 From: Ihor Volokhovych Date: Mon, 24 Oct 2022 01:00:36 +0300 Subject: [PATCH 56/56] Added swagger title --- Server/Server.Host/Extensions/SwaggerExtension.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Server/Server.Host/Extensions/SwaggerExtension.cs b/Server/Server.Host/Extensions/SwaggerExtension.cs index 08828ac..e822de2 100644 --- a/Server/Server.Host/Extensions/SwaggerExtension.cs +++ b/Server/Server.Host/Extensions/SwaggerExtension.cs @@ -70,9 +70,11 @@ public static IApplicationBuilder UseSwaggerUI( applicationBuilder.UseSwaggerUI(swaggerUiOptions => { - var title = AppDomain.CurrentDomain.FriendlyName; var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; - swaggerUiOptions.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{title} v{version}"); + var title = $"{AppDomain.CurrentDomain.FriendlyName} v{version}"; + + swaggerUiOptions.DocumentTitle = title; + swaggerUiOptions.SwaggerEndpoint($"/swagger/{version}/swagger.json", title); }); return applicationBuilder;