From ab6ef0dde8eedc82faba20756e07d289171c1bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Janus?= Date: Fri, 31 Mar 2023 18:11:04 +0200 Subject: [PATCH] First Release --- .editorconfig | 4 + .gitattributes | 63 +++++ .gitignore | 1 + Docs/API_Docs.xml | 8 + PKiK_Server.sln | 60 +++++ Server/.config/appsettings.json | 21 ++ Server/.tools/gen_secret_key.py | 6 + Server/API/API.csproj | 22 ++ Server/API/Controllers/KeyController.cs | 97 +++++++ Server/API/Controllers/LoginController.cs | 43 +++ Server/API/Controllers/MessageController.cs | 109 ++++++++ Server/API/Controllers/UserController.cs | 151 +++++++++++ Server/API/Program.cs | 63 +++++ Server/API/Properties/launchSettings.json | 41 +++ Server/API/Result.cs | 39 +++ Server/DB/DB.csproj | 26 ++ Server/DB/DBO/EventDBO.cs | 20 ++ Server/DB/DBO/JwtDBO.cs | 19 ++ Server/DB/DBO/KeyDBO.cs | 22 ++ Server/DB/DBO/MessageDBO.cs | 22 ++ Server/DB/DBO/RecipientDBO.cs | 22 ++ Server/DB/DBO/UserDBO.cs | 24 ++ Server/DB/DataContext.cs | 64 +++++ .../20221228153526_DBv3.0.Designer.cs | 212 +++++++++++++++ Server/DB/Migrations/20221228153526_DBv3.0.cs | 155 +++++++++++ .../20230104191729_DBv3.1.Designer.cs | 213 +++++++++++++++ Server/DB/Migrations/20230104191729_DBv3.1.cs | 41 +++ .../20230109193932_DBv3.2.Designer.cs | 250 ++++++++++++++++++ Server/DB/Migrations/20230109193932_DBv3.2.cs | 47 ++++ .../DB/Migrations/DataContextModelSnapshot.cs | 247 +++++++++++++++++ .../Interfaces/IAuthenticationService.cs | 12 + Server/Services/Interfaces/IKeyService.cs | 11 + Server/Services/Interfaces/ILoginService.cs | 11 + Server/Services/Interfaces/IMessageService.cs | 13 + Server/Services/Interfaces/IUserService.cs | 17 ++ Server/Services/Logging.cs | 68 +++++ Server/Services/ObjectMapper/KeyMapper.cs | 23 ++ Server/Services/ObjectMapper/MessageMapper.cs | 40 +++ .../Services/ObjectMapper/RecipientMapper.cs | 38 +++ Server/Services/ObjectMapper/UserMapper.cs | 42 +++ Server/Services/Services.csproj | 21 ++ .../Services/AuthenticationService.cs | 67 +++++ Server/Services/Services/KeyService.cs | 72 +++++ Server/Services/Services/LoginService.cs | 103 ++++++++ Server/Services/Services/MessageService.cs | 98 +++++++ Server/Services/Services/UserService.cs | 176 ++++++++++++ Server/Services/TokenGenerator.cs | 34 +++ Server/Shared/Config/Config.cs | 52 ++++ Server/Shared/Config/JWTConfig.cs | 39 +++ Server/Shared/EnvironmentalSettings.cs | 27 ++ Server/Shared/ServerShared.csproj | 15 ++ Shared/DTO/KeyDTO.cs | 14 + Shared/DTO/MessageDTO.cs | 28 ++ Shared/DTO/RecipientDTO.cs | 17 ++ Shared/DTO/UserDTO.cs | 19 ++ Shared/Enums/EventType.cs | 8 + Shared/OperationResult.cs | 39 +++ Shared/ResponseValidator.cs | 46 ++++ Shared/Shared.csproj | 18 ++ Tests/Server/DataGenerator.cs | 31 +++ Tests/Server/KeyServiceTest.cs | 24 ++ Tests/Server/LoginServiceTest.cs | 37 +++ Tests/Server/MessageServiceTest.cs | 29 ++ Tests/Server/Mocker.cs | 16 ++ Tests/Server/ServerTests.csproj | 32 +++ Tests/Server/UserServiceTest.cs | 28 ++ Tests/Server/usings.cs | 5 + 67 files changed, 3482 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 Docs/API_Docs.xml create mode 100644 PKiK_Server.sln create mode 100644 Server/.config/appsettings.json create mode 100644 Server/.tools/gen_secret_key.py create mode 100644 Server/API/API.csproj create mode 100644 Server/API/Controllers/KeyController.cs create mode 100644 Server/API/Controllers/LoginController.cs create mode 100644 Server/API/Controllers/MessageController.cs create mode 100644 Server/API/Controllers/UserController.cs create mode 100644 Server/API/Program.cs create mode 100644 Server/API/Properties/launchSettings.json create mode 100644 Server/API/Result.cs create mode 100644 Server/DB/DB.csproj create mode 100644 Server/DB/DBO/EventDBO.cs create mode 100644 Server/DB/DBO/JwtDBO.cs create mode 100644 Server/DB/DBO/KeyDBO.cs create mode 100644 Server/DB/DBO/MessageDBO.cs create mode 100644 Server/DB/DBO/RecipientDBO.cs create mode 100644 Server/DB/DBO/UserDBO.cs create mode 100644 Server/DB/DataContext.cs create mode 100644 Server/DB/Migrations/20221228153526_DBv3.0.Designer.cs create mode 100644 Server/DB/Migrations/20221228153526_DBv3.0.cs create mode 100644 Server/DB/Migrations/20230104191729_DBv3.1.Designer.cs create mode 100644 Server/DB/Migrations/20230104191729_DBv3.1.cs create mode 100644 Server/DB/Migrations/20230109193932_DBv3.2.Designer.cs create mode 100644 Server/DB/Migrations/20230109193932_DBv3.2.cs create mode 100644 Server/DB/Migrations/DataContextModelSnapshot.cs create mode 100644 Server/Services/Interfaces/IAuthenticationService.cs create mode 100644 Server/Services/Interfaces/IKeyService.cs create mode 100644 Server/Services/Interfaces/ILoginService.cs create mode 100644 Server/Services/Interfaces/IMessageService.cs create mode 100644 Server/Services/Interfaces/IUserService.cs create mode 100644 Server/Services/Logging.cs create mode 100644 Server/Services/ObjectMapper/KeyMapper.cs create mode 100644 Server/Services/ObjectMapper/MessageMapper.cs create mode 100644 Server/Services/ObjectMapper/RecipientMapper.cs create mode 100644 Server/Services/ObjectMapper/UserMapper.cs create mode 100644 Server/Services/Services.csproj create mode 100644 Server/Services/Services/AuthenticationService.cs create mode 100644 Server/Services/Services/KeyService.cs create mode 100644 Server/Services/Services/LoginService.cs create mode 100644 Server/Services/Services/MessageService.cs create mode 100644 Server/Services/Services/UserService.cs create mode 100644 Server/Services/TokenGenerator.cs create mode 100644 Server/Shared/Config/Config.cs create mode 100644 Server/Shared/Config/JWTConfig.cs create mode 100644 Server/Shared/EnvironmentalSettings.cs create mode 100644 Server/Shared/ServerShared.csproj create mode 100644 Shared/DTO/KeyDTO.cs create mode 100644 Shared/DTO/MessageDTO.cs create mode 100644 Shared/DTO/RecipientDTO.cs create mode 100644 Shared/DTO/UserDTO.cs create mode 100644 Shared/Enums/EventType.cs create mode 100644 Shared/OperationResult.cs create mode 100644 Shared/ResponseValidator.cs create mode 100644 Shared/Shared.csproj create mode 100644 Tests/Server/DataGenerator.cs create mode 100644 Tests/Server/KeyServiceTest.cs create mode 100644 Tests/Server/LoginServiceTest.cs create mode 100644 Tests/Server/MessageServiceTest.cs create mode 100644 Tests/Server/Mocker.cs create mode 100644 Tests/Server/ServerTests.csproj create mode 100644 Tests/Server/UserServiceTest.cs create mode 100644 Tests/Server/usings.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bddcb96 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = silent diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index dfcfd56..5403e17 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ +/Server/DB/.scripts diff --git a/Docs/API_Docs.xml b/Docs/API_Docs.xml new file mode 100644 index 0000000..e2b3a2b --- /dev/null +++ b/Docs/API_Docs.xml @@ -0,0 +1,8 @@ + + + + API + + + + diff --git a/PKiK_Server.sln b/PKiK_Server.sln new file mode 100644 index 0000000..0fb7712 --- /dev/null +++ b/PKiK_Server.sln @@ -0,0 +1,60 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33103.184 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "Server\API\API.csproj", "{B1CD2437-2432-4E55-B757-C602BA992480}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DB", "Server\DB\DB.csproj", "{3093A513-C714-4BA9-A735-07DECCF94A46}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "Server\Services\Services.csproj", "{35E2BFF4-CD1D-4252-9024-13B951836D52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerShared", "Server\Shared\ServerShared.csproj", "{F1E20FA7-41E0-4B0F-861A-6E43C7A87526}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{99E9891B-BADA-4C60-9059-AD7B377874C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{607D7389-1451-4569-BA28-BCD0E4C5E0FB}" + ProjectSection(SolutionItems) = preProject + Server\.config\appsettings.json = Server\.config\appsettings.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerTests", "Tests\Server\ServerTests.csproj", "{FF0C5CAA-0686-408A-9FCC-8CBAA33FC809}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1CD2437-2432-4E55-B757-C602BA992480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1CD2437-2432-4E55-B757-C602BA992480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1CD2437-2432-4E55-B757-C602BA992480}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1CD2437-2432-4E55-B757-C602BA992480}.Release|Any CPU.Build.0 = Release|Any CPU + {3093A513-C714-4BA9-A735-07DECCF94A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3093A513-C714-4BA9-A735-07DECCF94A46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3093A513-C714-4BA9-A735-07DECCF94A46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3093A513-C714-4BA9-A735-07DECCF94A46}.Release|Any CPU.Build.0 = Release|Any CPU + {35E2BFF4-CD1D-4252-9024-13B951836D52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35E2BFF4-CD1D-4252-9024-13B951836D52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35E2BFF4-CD1D-4252-9024-13B951836D52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35E2BFF4-CD1D-4252-9024-13B951836D52}.Release|Any CPU.Build.0 = Release|Any CPU + {F1E20FA7-41E0-4B0F-861A-6E43C7A87526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1E20FA7-41E0-4B0F-861A-6E43C7A87526}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1E20FA7-41E0-4B0F-861A-6E43C7A87526}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1E20FA7-41E0-4B0F-861A-6E43C7A87526}.Release|Any CPU.Build.0 = Release|Any CPU + {99E9891B-BADA-4C60-9059-AD7B377874C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99E9891B-BADA-4C60-9059-AD7B377874C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99E9891B-BADA-4C60-9059-AD7B377874C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99E9891B-BADA-4C60-9059-AD7B377874C0}.Release|Any CPU.Build.0 = Release|Any CPU + {FF0C5CAA-0686-408A-9FCC-8CBAA33FC809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF0C5CAA-0686-408A-9FCC-8CBAA33FC809}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF0C5CAA-0686-408A-9FCC-8CBAA33FC809}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF0C5CAA-0686-408A-9FCC-8CBAA33FC809}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6CAF5DA4-E79B-4191-88EC-64312E9F538C} + EndGlobalSection +EndGlobal diff --git a/Server/.config/appsettings.json b/Server/.config/appsettings.json new file mode 100644 index 0000000..dca5028 --- /dev/null +++ b/Server/.config/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|Server\\DB\\DB.mdf;Integrated Security=True;TrustServerCertificate=True;" + }, + "Config": { + "IsDevelopmentEnvironment": true, + "JWT": { + "DaysValid": 7, + "Audience": "", + "Issuer": "PKiK_Messenging_App", + "SecretKey": "95a3fb93623946821bf441624c2ed4465de3addc0552a8f7393534c5ffaf537f" + } + } +} diff --git a/Server/.tools/gen_secret_key.py b/Server/.tools/gen_secret_key.py new file mode 100644 index 0000000..04e2567 --- /dev/null +++ b/Server/.tools/gen_secret_key.py @@ -0,0 +1,6 @@ +import secrets + +key = secrets.token_hex(32) +key_string = str(key) + +print(key_string) \ No newline at end of file diff --git a/Server/API/API.csproj b/Server/API/API.csproj new file mode 100644 index 0000000..11f48af --- /dev/null +++ b/Server/API/API.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + PKiK.Server.API + ..\..\.build\Server\bin\ + True + ..\..\Docs\API_Docs.xml + + + + + + + + + + + + + diff --git a/Server/API/Controllers/KeyController.cs b/Server/API/Controllers/KeyController.cs new file mode 100644 index 0000000..89ce6f1 --- /dev/null +++ b/Server/API/Controllers/KeyController.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using PKiK.Server.Services; +using PKiK.Shared; +using System; + +namespace PKiK.Server.API.Controllers { + [ApiController] + [Route("[controller]")] + public class KeyController : ControllerBase { + private readonly IKeyService keyService; + private readonly IAuthenticationService authService; + public KeyController(IKeyService keyService, IAuthenticationService authService) { + this.keyService = keyService; + this.authService = authService; + } + + [HttpGet("GetByID/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetKey([FromRoute] int ID, [FromRoute] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(keyService.GetKey(ID), "KeyController", "GetKey"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpGet("GetByUserID/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetKeyForUser([FromRoute] int ID, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(keyService.GetUserKey(ID), "KeyController", "GetUserKey"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPost("Add")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult PostKey([FromBody] KeyDTO key, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + if (!authService.IsUser(jwt, key.User.ID)) { + return new StatusCodeResult(403); + } + return Result.Pass(keyService.AddKey(key), "KeyController", "PostKey"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPut("Revoke/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult RevokeKey([FromRoute] int ID, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + int userID = authService.GetUserID(jwt); + return Result.Pass(keyService.RevokeKey(ID, userID), "KeyController", "RevokeKey"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + } +} diff --git a/Server/API/Controllers/LoginController.cs b/Server/API/Controllers/LoginController.cs new file mode 100644 index 0000000..be4d221 --- /dev/null +++ b/Server/API/Controllers/LoginController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.Services; +using PKiK.Shared; +using System; + +namespace PKiK.Server.API.Controllers { + [ApiController] + [Route("[controller]")] + public class LoginController : ControllerBase { + private readonly ILoginService loginService; + public LoginController(ILoginService loginService, IAuthenticationService authService) { + this.loginService = loginService; + } + + [HttpGet("Login")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult Login([FromHeader] string username, [FromHeader] string password) { + try { + return Result.Pass(loginService.Login(username, password), "LoginController", "Login"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPost("Register")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult Register([FromBody] UserDTO user) { + try { + return Result.Pass(loginService.Register(user), "LoginController", "Register"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + } +} diff --git a/Server/API/Controllers/MessageController.cs b/Server/API/Controllers/MessageController.cs new file mode 100644 index 0000000..93bbafc --- /dev/null +++ b/Server/API/Controllers/MessageController.cs @@ -0,0 +1,109 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.Services; +using PKiK.Shared; +using System; + +namespace PKiK.Server.API.Controllers { + [ApiController] + [Route("[controller]")] + public class MessageController : ControllerBase { + private readonly IMessageService messageService; + private readonly IAuthenticationService authService; + public MessageController(IMessageService messageService, IAuthenticationService authService) { + this.messageService = messageService; + this.authService = authService; + } + + [HttpGet("Get/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetMessage([FromRoute] int ID, [FromHeader] string jwt) { + try { + if(!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(messageService.Get(ID), "MessageController", "GetMessage"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpGet("GetForUser/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetForUser([FromRoute] int ID, [FromHeader] string jwt, [FromHeader] int Count = 10, [FromHeader] int Index = 0) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(messageService.GetForUser(ID, Count, Index), "MessageController", "GetForUser"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPost("Send")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult PostMessage([FromBody] MessageDTO message, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + if(!authService.IsUser(jwt, message.Sender.ID)) { + return new StatusCodeResult(403); + } + return Result.Pass(messageService.Send(message), "MessageController", "SendMessage"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPut("SetSeen/{ID}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult SetSeen([FromRoute] int ID, [FromHeader] int UserID, [FromHeader] DateTime Time, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(messageService.SetSeen(ID, UserID, Time), "MessageController", "SetSeen"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpDelete("Delete/{ID}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult DeleteMessage([FromRoute] int ID, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + int userID = authService.GetUserID(jwt); + return Result.Pass(messageService.Delete(ID, userID), "MessageController", "DeleteMessage"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + } +} diff --git a/Server/API/Controllers/UserController.cs b/Server/API/Controllers/UserController.cs new file mode 100644 index 0000000..7542153 --- /dev/null +++ b/Server/API/Controllers/UserController.cs @@ -0,0 +1,151 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.Services; +using PKiK.Shared; +using System; + +namespace PKiK.Server.API.Controllers { + [ApiController] + [Route("[controller]")] + public class UserController : ControllerBase { + private readonly IUserService userService; + private readonly IAuthenticationService authService; + public UserController(IUserService userService, IAuthenticationService authService) { + this.userService = userService; + this.authService = authService; + } + + #region UserManagement + #region Get + [HttpGet("GetByID/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetUserByID([FromRoute] int ID, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.GetUser(ID), "UserController", "GetByID"); + }catch(Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpGet("GetByUsername/{Username}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetUserByUsername([FromRoute] string Username, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.GetUser(Username), "UserController", "GetByID"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + #endregion + + #region Post + [HttpPost("Add")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult PostUser([FromBody] UserDTO user, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.AddUser(user), "UserController", "PostUser"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpPut("Modify")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult UpdateUser([FromBody] UserDTO user, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + if (!authService.IsUser(jwt, user.ID)) { + return new StatusCodeResult(403); + } + return Result.Pass(userService.ModifyUser(user), "UserController", "UpdateUser"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpDelete("Delete/{ID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult DeleteUser([FromRoute] int ID, [FromHeader] string jwt) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.RemoveUser(ID), "UserController", "DeleteUser"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + #endregion + #endregion + + #region SearchUser + [HttpGet("SearchValue/{value}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult SearchUserByValue([FromRoute] string value, [FromHeader] string jwt, [FromHeader] int Count = 100, [FromHeader] bool ExactMatch = false) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.SearchUser(value, Count, ExactMatch), "UserController", "SearchValue"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + + [HttpGet("SearchByKey/{key}/{value}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult SearchUserByValue([FromRoute] string key, [FromRoute] string value, [FromHeader] string jwt, [FromHeader] int Count = 100, [FromHeader] bool ExactMatch = false) { + try { + if (!authService.IsValid(jwt)) { + return new StatusCodeResult(401); + } + return Result.Pass(userService.SearchUser(key, value, Count, ExactMatch), "UserController", "SearchByKey"); + } catch (Exception exc) { + Log.Event(exc); + return new StatusCodeResult(500); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Server/API/Program.cs b/Server/API/Program.cs new file mode 100644 index 0000000..b831ec5 --- /dev/null +++ b/Server/API/Program.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using PKiK.Server.DB; +using PKiK.Server.Services; +using PKiK.Server.Services.Services; +using PKiK.Server.Shared; +using System; + +namespace PKiK.Server.API { + public class Program { + private static IConfigurationRoot? configuration; + private static EnvironmentalSettings? environment; + public static void Main(string[] args) { + try { + var builder = WebApplication.CreateBuilder(args); + SetConfig(); + if (configuration == null) { throw new Exception("Cannot obtain configuration!"); } + Config? config = configuration.GetSection("Config").Get() ?? null; + if (config == null) { throw new Exception("Cannot read appsettings.json!"); } + config.ConnectionString = configuration.GetConnectionString("DefaultConnection"); + Config.Set(config); + builder.Services.AddDbContext(options => options.UseSqlServer(config.ConnectionString)); + //Create default user account for deleted accounts + using (var context = new DataContext()) { + context.CreateDefaultUsers(); + } + // Add services to the container. + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + var app = builder.Build(); + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) { + app.UseSwagger(); + app.UseSwaggerUI(); + } + app.UseHttpsRedirection(); + app.UseAuthorization(); + app.MapControllers(); + app.Run(); + }catch(Exception exc) { + Console.WriteLine("Fatal error: "+exc.ToString()); + Console.ReadKey(); //Wait for user to quit + } + } + private static void SetConfig() { + environment = EnvironmentalSettings.Get(); + configuration = new ConfigurationBuilder() + .SetBasePath(environment.AppSettingsPath) + .AddJsonFile("appsettings.json") + .Build(); + } + } +} \ No newline at end of file diff --git a/Server/API/Properties/launchSettings.json b/Server/API/Properties/launchSettings.json new file mode 100644 index 0000000..ca378bd --- /dev/null +++ b/Server/API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:26694", + "sslPort": 44349 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5132", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7120;http://localhost:5132", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Server/API/Result.cs b/Server/API/Result.cs new file mode 100644 index 0000000..a931ed8 --- /dev/null +++ b/Server/API/Result.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.Services; +using PKiK.Shared; + +namespace PKiK.Server.API { + public static class Result { + public static IActionResult Pass(IActionResult result, string controller, string method) { + if(result.GetType() == typeof(ObjectResult)) { + return PassObjectResult((ObjectResult)result, controller, method); + } + if (result.GetType() == typeof(StatusCodeResult)) { + return PassStatusCodeResult((StatusCodeResult)result, controller, method); + } + return result; + } + + private static IActionResult PassObjectResult(ObjectResult result, string controller, string method) { + if (!ResponseValidator.IsSuccess(result.StatusCode ?? 200)) { + string message = controller + ", " + method; + if (result.StatusCode.HasValue) { + message += ": " + result.StatusCode.Value.ToString(); + } + if(result.Value != null) { + message += ": " + result.Value.ToString(); + } + Log.Event(message); + } + return result; + } + + private static IActionResult PassStatusCodeResult(StatusCodeResult result, string controller, string method) { + if(!ResponseValidator.IsSuccess((int)result.StatusCode)) { + string message = controller + ", " + method + ": " + result.StatusCode.ToString(); + Log.Event(message); + } + return result; + } + } +} diff --git a/Server/DB/DB.csproj b/Server/DB/DB.csproj new file mode 100644 index 0000000..6183fac --- /dev/null +++ b/Server/DB/DB.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + PKiK.Server.DB + ..\..\.build\Server\bin\ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Server/DB/DBO/EventDBO.cs b/Server/DB/DBO/EventDBO.cs new file mode 100644 index 0000000..a9ed227 --- /dev/null +++ b/Server/DB/DBO/EventDBO.cs @@ -0,0 +1,20 @@ +using PKiK.Shared; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PKiK.Server.DB { + public class EventDBO { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int? ID { get; set; } + public virtual DateTime? DateTime { get; set; } + public virtual EventType? Type { get; set; } + [MaxLength(250)] + public virtual string? Message { get; set; } + [MaxLength(250)] + public virtual string? Inner { get; set; } + [MaxLength(100)] + public virtual string? Trace { get; set; } + } +} diff --git a/Server/DB/DBO/JwtDBO.cs b/Server/DB/DBO/JwtDBO.cs new file mode 100644 index 0000000..0d046ff --- /dev/null +++ b/Server/DB/DBO/JwtDBO.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PKiK.Server.DB { + public class JwtDBO { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int ID { get; set; } + public virtual UserDBO User { get; set; } + [MaxLength(2048)] + public virtual string JWT { get; set; } + public virtual bool Active { get; set; } + + public JwtDBO() { + JWT = string.Empty; + User = new UserDBO(); + Active = true; + } + } +} diff --git a/Server/DB/DBO/KeyDBO.cs b/Server/DB/DBO/KeyDBO.cs new file mode 100644 index 0000000..7cbf1f0 --- /dev/null +++ b/Server/DB/DBO/KeyDBO.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PKiK.Server.DB.DBO { + public class KeyDBO { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int ID { get; set; } + [ForeignKey("UserID")] + public virtual UserDBO User { get; set; } + //TODO: MaxLength + [Required] + public string PublicKey { get; set; } + public bool Active { get; set; } + + public KeyDBO() { + PublicKey = ""; + User = new UserDBO(); + Active = true; + } + } +} diff --git a/Server/DB/DBO/MessageDBO.cs b/Server/DB/DBO/MessageDBO.cs new file mode 100644 index 0000000..f8a8d99 --- /dev/null +++ b/Server/DB/DBO/MessageDBO.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PKiK.Server.DB { + public class MessageDBO { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int ID { get; set; } + [ForeignKey("SenderID")] + public virtual UserDBO Sender { get; set; } + [ForeignKey("RecipientID")] + public virtual ICollection Recipients { get; set; } + public virtual DateTime SentTime { get; set; } + + public MessageDBO() { + Sender = new UserDBO(); + Recipients = new List(); + } + } +} diff --git a/Server/DB/DBO/RecipientDBO.cs b/Server/DB/DBO/RecipientDBO.cs new file mode 100644 index 0000000..fc75c52 --- /dev/null +++ b/Server/DB/DBO/RecipientDBO.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace PKiK.Server.DB { + public class RecipientDBO { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int ID { get; set; } + [ForeignKey("UserID")] + public virtual UserDBO User { get; set; } + [Required] + public virtual int UserID { get; set; } + public virtual string Content { get; set; } + public virtual DateTime? SeenTime { get; set; } //null if not seen + + public RecipientDBO() { + User = new UserDBO(); + Content = string.Empty; + } + } +} diff --git a/Server/DB/DBO/UserDBO.cs b/Server/DB/DBO/UserDBO.cs new file mode 100644 index 0000000..f91dfb8 --- /dev/null +++ b/Server/DB/DBO/UserDBO.cs @@ -0,0 +1,24 @@ +using PKiK.Server.DB.DBO; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PKiK.Server.DB { + public class UserDBO { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public virtual int ID { get; set; } + [Required] + [MaxLength(50)] + public virtual string Username { get; set; } + [MaxLength(50)] + public virtual string? Name { get; set; } + [MaxLength(50)] + public virtual string? Surname { get; set; } + [Required] + public virtual string Password { get; set; } + public UserDBO() { + Username = ""; + Password = ""; + } + } +} diff --git a/Server/DB/DataContext.cs b/Server/DB/DataContext.cs new file mode 100644 index 0000000..421c8a2 --- /dev/null +++ b/Server/DB/DataContext.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using PKiK.Server.DB.DBO; +using PKiK.Server.Shared; +using System; +using System.IO; +using System.Linq; + +namespace PKiK.Server.DB { + public class DataContext : DbContext { + private static DataContext? instance; + public DataContext() { instance = this;} + public DataContext(DbContextOptions options) : base(options) { instance = this; } + public static string? ReadConnectionString() { + EnvironmentalSettings env = EnvironmentalSettings.Get(); + string[] cfg = File.ReadAllLines(env.AppSettingsPath+"//appsettings.json"); + string? connStr = null; + foreach(var line in cfg) { + if(line.Contains("DefaultConnection")) { + connStr = line.Split(':')[1]; + connStr = connStr.Trim(); + connStr = connStr.Trim('\"'); + connStr = connStr.Replace("\\\\", "\\"); + break; + } + } + return connStr; + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + AppDomain.CurrentDomain.SetData("DataDirectory", EnvironmentalSettings.Get().RootPath); + Config config = Config.Get(); + string? connStr = null; + if(config.ConnectionString == null) { + connStr = ReadConnectionString(); + } else { + connStr = config.ConnectionString; + } + optionsBuilder.UseLazyLoadingProxies().UseSqlServer(connStr); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity().HasOne(x => x.Sender).WithMany().OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity().HasMany(x => x.Recipients).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(x => x.User).WithMany().OnDelete(DeleteBehavior.Restrict); + } + + public void CreateDefaultUsers() { + if (!this.Users.Any(x => x.Username == "removed")) { + this.Users.Add(new UserDBO() { + Username = "removed", + Name = "User", + Surname = "Removed", + Password = "" + }); + this.SaveChanges(); + } + } + + public DbSet Users { get; set; } + public DbSet Messages { get; set; } + public DbSet Recipients { get; set; } + public DbSet Events { get; set; } + public DbSet Keys { get; set; } + public DbSet Jwt { get; set; } + } +} \ No newline at end of file diff --git a/Server/DB/Migrations/20221228153526_DBv3.0.Designer.cs b/Server/DB/Migrations/20221228153526_DBv3.0.Designer.cs new file mode 100644 index 0000000..605101e --- /dev/null +++ b/Server/DB/Migrations/20221228153526_DBv3.0.Designer.cs @@ -0,0 +1,212 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PKiK.Server.DB; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20221228153526_DBv3.0")] + partial class DBv30 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("PKiK.Server.DB.EventDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Inner") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Message") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Trace") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("SenderID") + .HasColumnType("int"); + + b.Property("SentTime") + .HasColumnType("datetime2"); + + b.HasKey("ID"); + + b.HasIndex("SenderID"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientID") + .HasColumnType("int"); + + b.Property("SeenTime") + .HasColumnType("datetime2"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("RecipientID"); + + b.HasIndex("UserID"); + + b.ToTable("Recipients"); + }); + + modelBuilder.Entity("PKiK.Server.DB.UserDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("ID"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "Sender") + .WithMany() + .HasForeignKey("SenderID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.HasOne("PKiK.Server.DB.MessageDBO", null) + .WithMany("Recipients") + .HasForeignKey("RecipientID"); + + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20221228153526_DBv3.0.cs b/Server/DB/Migrations/20221228153526_DBv3.0.cs new file mode 100644 index 0000000..4a6aa52 --- /dev/null +++ b/Server/DB/Migrations/20221228153526_DBv3.0.cs @@ -0,0 +1,155 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + /// + public partial class DBv30 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Events", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DateTime = table.Column(type: "datetime2", nullable: true), + Type = table.Column(type: "int", nullable: true), + Message = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: true), + Inner = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: true), + Trace = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Events", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Username = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Surname = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Password = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Keys", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserID = table.Column(type: "int", nullable: false), + PublicKey = table.Column(type: "nvarchar(max)", nullable: false), + Active = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Keys", x => x.ID); + table.ForeignKey( + name: "FK_Keys_Users_UserID", + column: x => x.UserID, + principalTable: "Users", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Messages", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + SenderID = table.Column(type: "int", nullable: false), + SentTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Messages", x => x.ID); + table.ForeignKey( + name: "FK_Messages_Users_SenderID", + column: x => x.SenderID, + principalTable: "Users", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Recipients", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserID = table.Column(type: "int", nullable: false), + Content = table.Column(type: "nvarchar(max)", nullable: false), + SeenTime = table.Column(type: "datetime2", nullable: true), + RecipientID = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Recipients", x => x.ID); + table.ForeignKey( + name: "FK_Recipients_Messages_RecipientID", + column: x => x.RecipientID, + principalTable: "Messages", + principalColumn: "ID"); + table.ForeignKey( + name: "FK_Recipients_Users_UserID", + column: x => x.UserID, + principalTable: "Users", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Keys_UserID", + table: "Keys", + column: "UserID"); + + migrationBuilder.CreateIndex( + name: "IX_Messages_SenderID", + table: "Messages", + column: "SenderID"); + + migrationBuilder.CreateIndex( + name: "IX_Recipients_RecipientID", + table: "Recipients", + column: "RecipientID"); + + migrationBuilder.CreateIndex( + name: "IX_Recipients_UserID", + table: "Recipients", + column: "UserID"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Events"); + + migrationBuilder.DropTable( + name: "Keys"); + + migrationBuilder.DropTable( + name: "Recipients"); + + migrationBuilder.DropTable( + name: "Messages"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Server/DB/Migrations/20230104191729_DBv3.1.Designer.cs b/Server/DB/Migrations/20230104191729_DBv3.1.Designer.cs new file mode 100644 index 0000000..5ee7ed7 --- /dev/null +++ b/Server/DB/Migrations/20230104191729_DBv3.1.Designer.cs @@ -0,0 +1,213 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PKiK.Server.DB; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230104191729_DBv3.1")] + partial class DBv31 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("PKiK.Server.DB.EventDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Inner") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Message") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Trace") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("SenderID") + .HasColumnType("int"); + + b.Property("SentTime") + .HasColumnType("datetime2"); + + b.HasKey("ID"); + + b.HasIndex("SenderID"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientID") + .HasColumnType("int"); + + b.Property("SeenTime") + .HasColumnType("datetime2"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("RecipientID"); + + b.HasIndex("UserID"); + + b.ToTable("Recipients"); + }); + + modelBuilder.Entity("PKiK.Server.DB.UserDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("ID"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "Sender") + .WithMany() + .HasForeignKey("SenderID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.HasOne("PKiK.Server.DB.MessageDBO", null) + .WithMany("Recipients") + .HasForeignKey("RecipientID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20230104191729_DBv3.1.cs b/Server/DB/Migrations/20230104191729_DBv3.1.cs new file mode 100644 index 0000000..b2b6133 --- /dev/null +++ b/Server/DB/Migrations/20230104191729_DBv3.1.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + /// + public partial class DBv31 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Recipients_Messages_RecipientID", + table: "Recipients"); + + migrationBuilder.AddForeignKey( + name: "FK_Recipients_Messages_RecipientID", + table: "Recipients", + column: "RecipientID", + principalTable: "Messages", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Recipients_Messages_RecipientID", + table: "Recipients"); + + migrationBuilder.AddForeignKey( + name: "FK_Recipients_Messages_RecipientID", + table: "Recipients", + column: "RecipientID", + principalTable: "Messages", + principalColumn: "ID"); + } + } +} diff --git a/Server/DB/Migrations/20230109193932_DBv3.2.Designer.cs b/Server/DB/Migrations/20230109193932_DBv3.2.Designer.cs new file mode 100644 index 0000000..76510d8 --- /dev/null +++ b/Server/DB/Migrations/20230109193932_DBv3.2.Designer.cs @@ -0,0 +1,250 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PKiK.Server.DB; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230109193932_DBv3.2")] + partial class DBv32 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("PKiK.Server.DB.EventDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Inner") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Message") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Trace") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("PKiK.Server.DB.JwtDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("JWT") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Jwt"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("SenderID") + .HasColumnType("int"); + + b.Property("SentTime") + .HasColumnType("datetime2"); + + b.HasKey("ID"); + + b.HasIndex("SenderID"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientID") + .HasColumnType("int"); + + b.Property("SeenTime") + .HasColumnType("datetime2"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("RecipientID"); + + b.HasIndex("UserID"); + + b.ToTable("Recipients"); + }); + + modelBuilder.Entity("PKiK.Server.DB.UserDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("ID"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.JwtDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "Sender") + .WithMany() + .HasForeignKey("SenderID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.HasOne("PKiK.Server.DB.MessageDBO", null) + .WithMany("Recipients") + .HasForeignKey("RecipientID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20230109193932_DBv3.2.cs b/Server/DB/Migrations/20230109193932_DBv3.2.cs new file mode 100644 index 0000000..423f45d --- /dev/null +++ b/Server/DB/Migrations/20230109193932_DBv3.2.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + /// + public partial class DBv32 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Jwt", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserID = table.Column(type: "int", nullable: false), + JWT = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), + Active = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Jwt", x => x.ID); + table.ForeignKey( + name: "FK_Jwt_Users_UserID", + column: x => x.UserID, + principalTable: "Users", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Jwt_UserID", + table: "Jwt", + column: "UserID"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Jwt"); + } + } +} diff --git a/Server/DB/Migrations/DataContextModelSnapshot.cs b/Server/DB/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..899dbf8 --- /dev/null +++ b/Server/DB/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,247 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PKiK.Server.DB; + +#nullable disable + +namespace PKiK.Server.DB.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("PKiK.Server.DB.EventDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Inner") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Message") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Trace") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("PKiK.Server.DB.JwtDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("JWT") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("UserID"); + + b.ToTable("Jwt"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("SenderID") + .HasColumnType("int"); + + b.Property("SentTime") + .HasColumnType("datetime2"); + + b.HasKey("ID"); + + b.HasIndex("SenderID"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RecipientID") + .HasColumnType("int"); + + b.Property("SeenTime") + .HasColumnType("datetime2"); + + b.Property("UserID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("RecipientID"); + + b.HasIndex("UserID"); + + b.ToTable("Recipients"); + }); + + modelBuilder.Entity("PKiK.Server.DB.UserDBO", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("ID"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PKiK.Server.DB.DBO.KeyDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.JwtDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.HasOne("PKiK.Server.DB.UserDBO", "Sender") + .WithMany() + .HasForeignKey("SenderID") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("PKiK.Server.DB.RecipientDBO", b => + { + b.HasOne("PKiK.Server.DB.MessageDBO", null) + .WithMany("Recipients") + .HasForeignKey("RecipientID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("PKiK.Server.DB.UserDBO", "User") + .WithMany() + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PKiK.Server.DB.MessageDBO", b => + { + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Services/Interfaces/IAuthenticationService.cs b/Server/Services/Interfaces/IAuthenticationService.cs new file mode 100644 index 0000000..975d8c8 --- /dev/null +++ b/Server/Services/Interfaces/IAuthenticationService.cs @@ -0,0 +1,12 @@ +using PKiK.Server.DB; +using System.Collections.Generic; + +namespace PKiK.Server.Services { + public interface IAuthenticationService { + public bool IsValid(string jwt); + public bool IsUser(string jwt, int userID); + public List GetUsersToken(int userID); + public int GetUserID(string jwt); + public bool CheckJwtValid(string jwt); + } +} diff --git a/Server/Services/Interfaces/IKeyService.cs b/Server/Services/Interfaces/IKeyService.cs new file mode 100644 index 0000000..2bed112 --- /dev/null +++ b/Server/Services/Interfaces/IKeyService.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; +using PKiK.Shared; + +namespace PKiK.Server.Services { + public interface IKeyService { + public IActionResult RevokeKey(int ID, int userID); + public IActionResult GetUserKey(int UserID); + public IActionResult AddKey(KeyDTO Key); + public IActionResult GetKey(int ID); + } +} diff --git a/Server/Services/Interfaces/ILoginService.cs b/Server/Services/Interfaces/ILoginService.cs new file mode 100644 index 0000000..bf8fcb6 --- /dev/null +++ b/Server/Services/Interfaces/ILoginService.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; +using PKiK.Shared; + +namespace PKiK.Server.Services { + public interface ILoginService { + public IActionResult Register(UserDTO user); + public IActionResult Login(string username, string password); + public IActionResult ValidateToken(string token); + public IActionResult RevokeToken(string token); + } +} diff --git a/Server/Services/Interfaces/IMessageService.cs b/Server/Services/Interfaces/IMessageService.cs new file mode 100644 index 0000000..824b26c --- /dev/null +++ b/Server/Services/Interfaces/IMessageService.cs @@ -0,0 +1,13 @@ +using PKiK.Shared; +using Microsoft.AspNetCore.Mvc; +using System; + +namespace PKiK.Server.Services { + public interface IMessageService { + public IActionResult Send(MessageDTO message); + public IActionResult Delete(int ID, int userID); + public IActionResult SetSeen(int ID, int UserID, DateTime time); + public IActionResult Get(int ID); + public IActionResult GetForUser(int UserID, int count, int index); + } +} diff --git a/Server/Services/Interfaces/IUserService.cs b/Server/Services/Interfaces/IUserService.cs new file mode 100644 index 0000000..03702fa --- /dev/null +++ b/Server/Services/Interfaces/IUserService.cs @@ -0,0 +1,17 @@ +using PKiK.Shared; +using Microsoft.AspNetCore.Mvc; + +namespace PKiK.Server.Services { + public interface IUserService { + public IActionResult AddUser(UserDTO user); + public IActionResult RemoveUser(int ID); + public IActionResult ModifyUser(UserDTO user); + public IActionResult GetUser(int ID); + public IActionResult GetUser(string username); + public IActionResult SearchUser(string value, int count = 100, bool ExactMatch = false); + public IActionResult SearchUser(string key, string value, int count = 100, bool ExactMatch = false); + public IActionResult SearchUserByUsername(string value, int count = 100, bool ExactMatch = false); + public IActionResult SearchUserByName(string value, int count = 100, bool ExactMatch = false); + public IActionResult SearchUserBySurname(string value, int count = 100, bool ExactMatch = false); + } +} diff --git a/Server/Services/Logging.cs b/Server/Services/Logging.cs new file mode 100644 index 0000000..08a7245 --- /dev/null +++ b/Server/Services/Logging.cs @@ -0,0 +1,68 @@ +using PKiK.Server.DB; +using PKiK.Shared; +using System; +using System.Text; + +namespace PKiK.Server.Services { + public static class Log { + public static void Event(string message, EventType type = EventType.INFO) { + EventDBO evt = new EventDBO(); + evt.Message = message; + evt.Type = type; + evt.DateTime = DateTime.Now; + Display(evt); + Save(evt); + } + public static void Event(Exception exc, string? message=null) { + EventDBO evt = new EventDBO(); + evt.Type = EventType.ERROR; + if(message!= null) { + evt.Message = message + " Exception: " + exc.Message; + } else { + evt.Message = exc.Message; + } + evt.DateTime = DateTime.Now; + if (exc.InnerException != null) { + evt.Inner = exc.InnerException.Message; + evt.Trace = exc.InnerException.StackTrace; + } else { + evt.Inner = null; + evt.Trace = null; + } + Display(evt); + Save(evt); + } + private static void Save(EventDBO evt) { + using (var context = new DataContext()) { + context.Events.Add(evt); + } + } + private static void Display(EventDBO evt) { + StringBuilder sb = new StringBuilder(); + if (evt.DateTime.HasValue) { + sb.Append(evt.DateTime.Value.ToString("G") + " "); + } + sb.Append(evt.Type.ToString() + ": "); + sb.Append(evt.Message); + if(evt.Inner != null) { + sb.Append("\nInner: " + evt.Inner); + } + if(evt.Trace != null) { + sb.Append("\nTrace: " + evt.Trace); + } + switch (evt.Type) { + case EventType.ERROR: + Console.ForegroundColor = ConsoleColor.Red; + break; + case EventType.WARNING: + Console.ForegroundColor = ConsoleColor.DarkYellow; + break; + case EventType.SUCCESS: + Console.ForegroundColor = ConsoleColor.DarkGreen; + break; + } + Console.WriteLine(sb.ToString()); + Console.ResetColor(); + } + } +} diff --git a/Server/Services/ObjectMapper/KeyMapper.cs b/Server/Services/ObjectMapper/KeyMapper.cs new file mode 100644 index 0000000..0ea3487 --- /dev/null +++ b/Server/Services/ObjectMapper/KeyMapper.cs @@ -0,0 +1,23 @@ +using PKiK.Server.DB.DBO; +using PKiK.Shared; + +namespace PKiK.Server.Services { + public static partial class ObjectMapper { + public static KeyDBO Key(KeyDTO key) { + return new KeyDBO { + ID = key.ID, + User = User(key.User), + PublicKey = key.PublicKey, + Active = key.Active + }; + } + public static KeyDTO Key(KeyDBO key) { + return new KeyDTO { + ID = key.ID, + User = User(key.User), + PublicKey = key.PublicKey, + Active = key.Active + }; + } + } +} diff --git a/Server/Services/ObjectMapper/MessageMapper.cs b/Server/Services/ObjectMapper/MessageMapper.cs new file mode 100644 index 0000000..cd4c617 --- /dev/null +++ b/Server/Services/ObjectMapper/MessageMapper.cs @@ -0,0 +1,40 @@ +using PKiK.Shared; +using PKiK.Server.DB; +using System.Collections.Generic; +using System.Linq; + +namespace PKiK.Server.Services { + public static partial class ObjectMapper { + public static MessageDBO Message(MessageDTO message) { + return new MessageDBO { + Sender = User(message.Sender), + SentTime = message.SentTime, + Recipients = Recipient(message.Recipients.ToList()), + }; + } + + public static MessageDTO Message(MessageDBO message) { + return new MessageDTO { + Sender = User(message.Sender), + SentTime = message.SentTime, + Recipients = Recipient(message.Recipients.ToList()), + }; + } + + public static List Message(List messages) { + List result = new List(); + foreach (MessageDTO message in messages) { + result.Add(Message(message)); + } + return result; + } + + public static List Message(List messages) { + List result = new List(); + foreach (MessageDBO message in messages) { + result.Add(Message(message)); + } + return result; + } + } +} diff --git a/Server/Services/ObjectMapper/RecipientMapper.cs b/Server/Services/ObjectMapper/RecipientMapper.cs new file mode 100644 index 0000000..980b8d1 --- /dev/null +++ b/Server/Services/ObjectMapper/RecipientMapper.cs @@ -0,0 +1,38 @@ +using PKiK.Server.DB; +using PKiK.Shared; +using System.Collections.Generic; + +namespace PKiK.Server.Services { + public static partial class ObjectMapper { + public static RecipientDBO Recipient(RecipientDTO recipient) { + return new RecipientDBO { + ID = recipient.ID, + User = User(recipient.User), + Content = recipient.Content, + SeenTime = recipient.SeenTime + }; + } + public static RecipientDTO Recipient(RecipientDBO recipient) { + return new RecipientDTO { + ID = recipient.ID, + User = User(recipient.User), + Content = recipient.Content, + SeenTime = recipient.SeenTime + }; + } + public static List Recipient(List recipients) { + List result = new List(); + foreach (RecipientDTO recipient in recipients) { + result.Add(Recipient(recipient)); + } + return result; + } + public static List Recipient(List recipients) { + List result = new List(); + foreach (RecipientDBO recipient in recipients) { + result.Add(Recipient(recipient)); + } + return result; + } + } +} diff --git a/Server/Services/ObjectMapper/UserMapper.cs b/Server/Services/ObjectMapper/UserMapper.cs new file mode 100644 index 0000000..19413e6 --- /dev/null +++ b/Server/Services/ObjectMapper/UserMapper.cs @@ -0,0 +1,42 @@ +using PKiK.Shared; +using PKiK.Server.DB; +using System.Collections.Generic; + +namespace PKiK.Server.Services { + public static partial class ObjectMapper { + public static UserDBO User(UserDTO user) { + return new UserDBO { + ID = user.ID, + Username = user.Username, + Name = user.Name, + Surname = user.Surname, + Password = user.Password + }; + } + + public static UserDTO User(UserDBO user) { + return new UserDTO { + ID = user.ID, + Username = user.Username, + Name = user.Name, + Surname = user.Surname + }; + } + + public static List User(List users) { + List result = new List(); + foreach (UserDTO user in users) { + result.Add(User(user)); + } + return result; + } + + public static List User(List users) { + List result = new List(); + foreach (UserDBO user in users) { + result.Add(User(user)); + } + return result; + } + } +} diff --git a/Server/Services/Services.csproj b/Server/Services/Services.csproj new file mode 100644 index 0000000..efa238f --- /dev/null +++ b/Server/Services/Services.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + PKiK.Server.Services + ..\..\.build\Server\bin\ + + + + + + + + + + + + + + diff --git a/Server/Services/Services/AuthenticationService.cs b/Server/Services/Services/AuthenticationService.cs new file mode 100644 index 0000000..203f35d --- /dev/null +++ b/Server/Services/Services/AuthenticationService.cs @@ -0,0 +1,67 @@ +using PKiK.Server.DB; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; + +namespace PKiK.Server.Services { + public class AuthenticationService : IAuthenticationService { + public bool CheckJwtValid(string jwt) { + JwtSecurityToken token; + try { + token = new JwtSecurityTokenHandler().ReadJwtToken(jwt); + } catch (Exception) { + return false; + } + + return token.ValidTo > DateTime.UtcNow; + } + + public List GetUsersToken(int userID) { + var result = new List(); + using (var context = new DataContext()) { + if(context.Jwt.Any(x => x.User.ID == userID && x.Active)) { + result = context.Jwt.Where(x => x.User.ID == userID && x.Active).ToList(); + } + } + return result; + } + + public int GetUserID(string jwt) { + var jwtHandler = new JwtSecurityTokenHandler(); + var token = jwtHandler.ReadToken(jwt) as JwtSecurityToken; + int id; + if (int.TryParse(token?.Payload["sub"].ToString(), out id)) { + return id; + } else { + throw new Exception("Malformed token!"); + } + } + + public bool IsUser(string jwt, int userID) { + int id = GetUserID(jwt); + if(id == userID) { + return true; + } else { + return false; + } + } + + public bool IsValid(string jwt) { + if (!CheckJwtValid(jwt)) { + return false; + } + var userTokens = GetUsersToken(GetUserID(jwt)); + if (userTokens == null || userTokens.Count < 1) { + throw new Exception("JWT user not found!"); + } + foreach (var userToken in userTokens) { + if(userToken.JWT == jwt) { + return true; + } + } + return false; + } + } +} diff --git a/Server/Services/Services/KeyService.cs b/Server/Services/Services/KeyService.cs new file mode 100644 index 0000000..0ab6fda --- /dev/null +++ b/Server/Services/Services/KeyService.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.DB; +using PKiK.Server.DB.DBO; +using PKiK.Shared; +using System.Linq; + +namespace PKiK.Server.Services.Services { + public class KeyService : IKeyService { + public IActionResult AddKey(KeyDTO Key) { + using (var context = new DataContext()) { + KeyDBO dbo = ObjectMapper.Key(Key); + if (context.Keys.Any(x => x.User.ID == Key.User.ID && x.Active == true)) { + return new StatusCodeResult(StatusCodes.Status409Conflict); //Cannot have multiple active keys + } + if (context.Users.Any(x => x.ID == Key.User.ID)) { + dbo.User = context.Users.Single(x => x.ID == Key.User.ID); + dbo.Active = true; + context.Keys.Add(dbo); + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status201Created); + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult GetKey(int ID) { + using (var context = new DataContext()) { + if (context.Keys.Any(x => x.ID == ID)) { + var dbo = context.Keys.Single(x => x.ID == ID); + dbo.User = context.Users.Single(x => x.ID == dbo.User.ID); + return new ObjectResult(ObjectMapper.Key(dbo)) { StatusCode = StatusCodes.Status200OK }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult GetUserKey(int UserID) { + using (var context = new DataContext()) { + if (context.Keys.Any(x => x.User.ID == UserID && x.Active == true)) { + KeyDBO dbo = context.Keys.Single(x => x.User.ID == UserID && x.Active == true); + dbo.User = context.Users.Single(x => x.ID == UserID); + return new ObjectResult(ObjectMapper.Key(dbo)) { StatusCode = StatusCodes.Status200OK }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult RevokeKey(int ID, int userID) { + using (var context = new DataContext()) { + if (context.Keys.Any(x => x.ID == ID)) { + if (context.Keys.Any(x => x.ID == ID && x.Active == true)) { + KeyDBO dbo = context.Keys.Single(x => x.ID == ID && x.Active == true); + if(dbo.User.ID != userID) { + return new StatusCodeResult(StatusCodes.Status403Forbidden); + } + dbo.Active = false; + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } else { + return new StatusCodeResult(StatusCodes.Status409Conflict); + } + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + } +} diff --git a/Server/Services/Services/LoginService.cs b/Server/Services/Services/LoginService.cs new file mode 100644 index 0000000..5d9021b --- /dev/null +++ b/Server/Services/Services/LoginService.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.DB; +using PKiK.Shared; +using System.Linq; + +namespace PKiK.Server.Services { + public class LoginService : ILoginService { + private readonly IAuthenticationService authService; + + public LoginService(IAuthenticationService authService) { + this.authService = authService; + } + + public IActionResult Register(UserDTO user) { + ObjectResult validation = ValidateUser(user); + if (validation.StatusCode == 200) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.Username == user.Username)) { + return new ObjectResult("User with same username already registered!") { StatusCode = StatusCodes.Status409Conflict }; + } + var dbo = ObjectMapper.User(user); + context.Add(dbo); + context.SaveChanges(); + var token = TokenGenerator.UserToken(dbo); + var jwtDBO = new JwtDBO() { + User = dbo, + JWT = token, + Active = true + }; + context.Jwt.Add(jwtDBO); + context.SaveChanges(); + return new ObjectResult(token) { StatusCode = StatusCodes.Status200OK }; + } + } else { + return validation; + } + } + + public IActionResult Login(string username, string password) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.Username == username)) { + UserDBO user = context.Users.Single(x => x.Username == username); + if (user.Password == password.Trim()) { + string? token = null; + if(context.Jwt.Any(x => x.User.ID == user.ID)) { + var userTokens = context.Jwt.Where(x => x.User.ID == user.ID).ToList(); + foreach (var userToken in userTokens) { + if (!authService.CheckJwtValid(userToken.JWT)){ + userToken.Active = false; //Deactivate expired token + } else { + token = userToken.JWT; + break; + } + } + } + if(token == null) { + token = TokenGenerator.UserToken(user); + } + return new ObjectResult(token) { StatusCode = StatusCodes.Status200OK }; + } else { + return new ObjectResult("Wrong username or password!") { StatusCode = StatusCodes.Status401Unauthorized }; + } + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult RevokeToken(string token) { + //TODO: Revoke Token + throw new System.NotImplementedException(); + } + + public IActionResult ValidateToken(string token) { + //TODO: Check if token is valid + throw new System.NotImplementedException(); + } + + private ObjectResult ValidateUser(UserDTO user) { + if(user.Username == null || user.Username.Length < 4) { + return new ObjectResult("Username not valid.") + { StatusCode = StatusCodes.Status406NotAcceptable }; + } + if(!ValidateString(user.Name ?? "")) { + return new ObjectResult("Name contains forbiden character.") + { StatusCode = StatusCodes.Status406NotAcceptable }; + } + if(!ValidateString(user.Surname ?? "")) { + return new ObjectResult("Surname contains forbiden character.") + { StatusCode = StatusCodes.Status406NotAcceptable }; + } + return new ObjectResult("") { StatusCode = 200 }; + } + + private bool ValidateString(string str) { + foreach(char c in str) { + if (!char.IsLetter(c)) { return false; } + } + return true; + } + } +} diff --git a/Server/Services/Services/MessageService.cs b/Server/Services/Services/MessageService.cs new file mode 100644 index 0000000..15d10b8 --- /dev/null +++ b/Server/Services/Services/MessageService.cs @@ -0,0 +1,98 @@ +using PKiK.Shared; +using Microsoft.AspNetCore.Mvc; +using System; +using PKiK.Server.DB; +using System.Linq; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; + +namespace PKiK.Server.Services { + public class MessageService : IMessageService { + public IActionResult Delete(int ID, int userID) { + using (var context = new DataContext()) { + if (context.Messages.Any(x => x.ID == ID)) { + var dbo = context.Messages.Single(x => x.ID == ID); + if(dbo.Sender.ID != userID) { + return new StatusCodeResult(StatusCodes.Status403Forbidden); + } + context.Messages.Remove(dbo); + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult Get(int ID) { + using (var context = new DataContext()) { + if (context.Messages.Any(x => x.ID == ID)) { + MessageDBO dbo = context.Messages.Single(x => x.ID == ID); + return new ObjectResult(ObjectMapper.Message(dbo)) { StatusCode = StatusCodes.Status200OK }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult GetForUser(int UserID, int count, int index) { + //TODO: Authorize user + List messages; + var context = new DataContext(); + if (!context.Users.Any(x => x.ID == UserID)) { + return new ObjectResult("User does not exists!") { StatusCode = StatusCodes.Status404NotFound }; + } else { + messages = context.Messages.Where(x => x.Sender.ID == UserID || x.Recipients.Any(u => u.User.ID == UserID)) + .OrderBy(x => x.SentTime).Skip(index).Take(count).ToList(); + return new ObjectResult(messages) { StatusCode = StatusCodes.Status200OK }; + } + } + + public IActionResult Send(MessageDTO message) { + var validation = message.Validate(); + if(!validation.IsSuccess) { + return validation.GetResult(); + } + using (var context = new DataContext()) { + MessageDBO messageDBO = ObjectMapper.Message(message); + if (context.Users.Any(x => x.ID == messageDBO.Sender.ID)) { + messageDBO.Sender = context.Users.Single(x => x.ID == messageDBO.Sender.ID); + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + foreach (var recipient in messageDBO.Recipients) { + if (context.Users.Any(x => x.ID == recipient.User.ID)) { + recipient.User = context.Users.Single(x => x.ID == recipient.User.ID); + recipient.SeenTime = null; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + context.Messages.Add(messageDBO); + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } + } + + public IActionResult SetSeen(int ID, int UserID, DateTime time) { + using (var context = new DataContext()) { + if (context.Messages.Any(x => x.ID == ID)) { + MessageDBO dbo = context.Messages.Single(x => x.ID == ID); + if (dbo.Recipients == null) { + return new StatusCodeResult(StatusCodes.Status409Conflict); + } + foreach (var recipient in dbo.Recipients) { + if (recipient.User.ID == UserID) { + recipient.SeenTime = time; + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } + } + return new ObjectResult("The provided user was not a recipient of provided message!") { StatusCode = StatusCodes.Status409Conflict }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + } +} diff --git a/Server/Services/Services/UserService.cs b/Server/Services/Services/UserService.cs new file mode 100644 index 0000000..ddf22e6 --- /dev/null +++ b/Server/Services/Services/UserService.cs @@ -0,0 +1,176 @@ +using PKiK.Shared; +using Microsoft.AspNetCore.Mvc; +using PKiK.Server.DB; +using System.Linq; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; + +namespace PKiK.Server.Services { + public class UserService : IUserService { + public IActionResult AddUser(UserDTO user) { + using (var context = new DataContext()) { + if (!context.Users.Any(x => x.Username == user.Username)) { + context.Users.Add(ObjectMapper.User(user)); + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status201Created); + } else { + return new ObjectResult("User with same username already exists!") { StatusCode = StatusCodes.Status409Conflict }; + } + } + } + + public IActionResult GetUser(int ID) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.ID == ID)) { + UserDBO dbo = context.Users.Single(x => x.ID == ID); + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult GetUser(string username) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.Username == username)) { + UserDBO dbo = context.Users.Single(x => x.Username == username); + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult ModifyUser(UserDTO user) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.ID == user.ID)) { + UserDBO dbo = context.Users.Single(x => x.ID == user.ID); + dbo.Username = user.Username; + dbo.Name = user.Name; + dbo.Surname = user.Surname; + dbo.Password = user.Password; + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult RemoveUser(int ID) { + using (var context = new DataContext()) { + if (context.Users.Any(x => x.ID == ID)) { + UserDBO dbo = context.Users.Single(x => x.ID == ID); + var messages = context.Messages.Where(x => x.Sender.ID == ID + || x.Recipients.Any(r => r.User.ID == ID)).ToList(); + var placeholder = context.Users.Single(x => x.Username == "removed"); + foreach (var message in messages) { + if (message.Sender.ID == ID) { + message.Sender = placeholder; + } + if (message.Recipients != null) { + foreach (var recipient in message.Recipients) { + if (recipient.User.ID == ID) { + recipient.User = placeholder; + } + } + } + } + context.Users.Remove(dbo); + context.SaveChanges(); + return new StatusCodeResult(StatusCodes.Status200OK); + } else { + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + } + } + + public IActionResult SearchUser(string value, int count = 100, bool ExactMatch = false) { + List dbo = new List(); + value = value.Trim(); + using (var context = new DataContext()) { + if (value == "*") { + dbo = context.Users.Take(count).ToList(); + } else { + if (ExactMatch) { + dbo = context.Users.Where(x => x.Username == value || x.Name == value || x.Surname == value).Take(count).ToList(); + } else { + dbo = context.Users.Where(x => (x.Username.Contains(value)) || + (!string.IsNullOrEmpty(x.Name) && x.Name.Contains(value)) || + (!string.IsNullOrEmpty(x.Surname) && x.Surname.Contains(value))).Take(count).ToList(); + } + } + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } + } + + public IActionResult SearchUser(string key, string value, int count = 100, bool ExactMatch = false) { + key = key.Trim().ToLower(); + value = value.Trim(); + switch (key) { + case "username": + case "uname": + return SearchUserByUsername(value, count, ExactMatch); + case "name": + case "firstname": + return SearchUserByName(value, count, ExactMatch); + case "surname": + case "lastname": + return SearchUserBySurname(value, count, ExactMatch); + default: + return new ObjectResult("Invalid key provided!") { StatusCode = StatusCodes.Status400BadRequest }; + } + } + + public IActionResult SearchUserByUsername(string value, int count = 100, bool ExactMatch = false ) { + value = value.Trim(); + List dbo = new List(); + using (var context = new DataContext()) { + if (value == "*") { + dbo = context.Users.Take(count).ToList(); + } else { + if (ExactMatch) { + dbo = context.Users.Where(x => x.Username == value).Take(count).ToList(); + } else { + dbo = context.Users.Where(x => (x.Username.Contains(value))).Take(count).ToList(); + } + } + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } + } + + public IActionResult SearchUserByName(string value, int count = 100, bool ExactMatch = false) { + value = value.Trim(); + List dbo = new List(); + using (var context = new DataContext()) { + if (value == "*") { + dbo = context.Users.Take(count).ToList(); + } else { + if (ExactMatch) { + dbo = context.Users.Where(x => x.Name == value).Take(count).ToList(); + } else { + dbo = context.Users.Where(x => (!string.IsNullOrEmpty(x.Name) && x.Name.Contains(value))).Take(count).ToList(); + } + } + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } + } + + public IActionResult SearchUserBySurname(string value, int count = 100, bool ExactMatch = false) { + value = value.Trim(); + List dbo = new List(); + using (var context = new DataContext()) { + if (value == "*") { + dbo = context.Users.Take(count).ToList(); + } else { + if (ExactMatch) { + dbo = context.Users.Where(x => x.Surname == value).Take(count).ToList(); + } else { + dbo = context.Users.Where(x => (!string.IsNullOrEmpty(x.Surname) && x.Surname.Contains(value))).Take(count).ToList(); + } + } + return new ObjectResult(ObjectMapper.User(dbo)) { StatusCode = StatusCodes.Status200OK }; + } + } + } +} diff --git a/Server/Services/TokenGenerator.cs b/Server/Services/TokenGenerator.cs new file mode 100644 index 0000000..c73cd45 --- /dev/null +++ b/Server/Services/TokenGenerator.cs @@ -0,0 +1,34 @@ +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using PKiK.Server.DB; +using PKiK.Server.Shared; +using System; +using System.Security.Claims; +using System.Text; + +namespace PKiK.Server.Services { + internal static class TokenGenerator { + internal static string UserToken(UserDBO user) { + var config = Config.Get(); + + DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + TimeSpan iat = DateTime.UtcNow - origin; + int intIAT = (int)Math.Floor(iat.TotalSeconds); + + TimeSpan exp = DateTime.UtcNow.AddDays(config.JWT.DaysValid) - origin; + int intEXP = (int)Math.Floor(exp.TotalSeconds); + + var claims = new[] { + new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()), + new Claim(JwtRegisteredClaimNames.Iat, intIAT.ToString()), + new Claim(JwtRegisteredClaimNames.Exp, intEXP.ToString()) + }; + var jwtToken = new JwtSecurityToken(config.JWT.Issuer, + config.JWT.Audience ?? "", claims, + signingCredentials: + new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JWT.SecretKey)), + SecurityAlgorithms.HmacSha256)); + return new JwtSecurityTokenHandler().WriteToken(jwtToken); + } + } +} diff --git a/Server/Shared/Config/Config.cs b/Server/Shared/Config/Config.cs new file mode 100644 index 0000000..a389f0f --- /dev/null +++ b/Server/Shared/Config/Config.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json.Serialization; + +namespace PKiK.Server.Shared { + public class Config { + [JsonPropertyName("IsDevelopmentEnvironment")] + public bool IsDevelopmentEnvironment { get; set; } + [JsonPropertyName("JWT")] + public JWTConfig JWT { get; set; } + [JsonIgnore] + public string? ConnectionString { get; set; } + [JsonIgnore] + private static Config? instance; + + public Config() { + JWT = new JWTConfig(); + } + + public static Config Get() { + if(instance == null) { + instance = new Config(); + } + return instance; + } + + public static void Set(Config config) { + instance = config; + } + + public bool IsValid { + get { + return JWT.IsValid; + } + } + + public static Config ReadConfig() { + var env = EnvironmentalSettings.Get(); + var configuration = new ConfigurationBuilder() + .SetBasePath(env.AppSettingsPath) + .AddJsonFile("appsettings.json").Build(); + var config = configuration.GetSection("Config").Get(); + if (config != null) { + config.ConnectionString = configuration.GetConnectionString("DefaultConnection"); + if (config.ConnectionString == null) { throw new Exception("Cannot obtain connection string!"); } + Set(config); + return config; + } else { + throw new Exception("Cannot read config!"); + } + } + } +} diff --git a/Server/Shared/Config/JWTConfig.cs b/Server/Shared/Config/JWTConfig.cs new file mode 100644 index 0000000..dab83ab --- /dev/null +++ b/Server/Shared/Config/JWTConfig.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace PKiK.Server.Shared { + public class JWTConfig { + /// + /// Number of days the token will be valid after generation + /// + public int DaysValid { get; set; } + /// + /// String or array of strings representing JWT Audience field + /// + public string? Audience { get; set; } + /// + /// Name of JWT Issuer + /// + public string? Issuer { get; set; } + /// + /// Secret key used to sign JWT tokens + /// + public string SecretKey { get; set; } + + public JWTConfig() { + SecretKey = string.Empty; + } + + [JsonIgnore] + public bool IsValid { + get { + if (string.IsNullOrEmpty(SecretKey) || SecretKey.Length < 64) { + return false; + } + if(DaysValid < 1) { + return false; + } + return true; + } + } + } +} diff --git a/Server/Shared/EnvironmentalSettings.cs b/Server/Shared/EnvironmentalSettings.cs new file mode 100644 index 0000000..28f6e94 --- /dev/null +++ b/Server/Shared/EnvironmentalSettings.cs @@ -0,0 +1,27 @@ +using System.Linq; +using System; + +namespace PKiK.Server.Shared { + public class EnvironmentalSettings { + private static EnvironmentalSettings? instance; + private EnvironmentalSettings() { + string dataDirectoryPath = AppDomain.CurrentDomain.BaseDirectory; + if (dataDirectoryPath.Contains(".build")) { + RootPath = dataDirectoryPath.Split(".build").First(); //TODO: Check different dir names + } else { + RootPath = dataDirectoryPath.Split("Publish").First(); + } + DBPath = RootPath + "Server//DB//DB.mdf"; + AppSettingsPath = RootPath + "//Server//.config//"; + } + public string RootPath { get; set; } + public string DBPath { get; set; } + public string AppSettingsPath { get; set; } + public static EnvironmentalSettings Get() { + if (instance == null) { + instance = new EnvironmentalSettings(); + } + return instance; + } + } +} diff --git a/Server/Shared/ServerShared.csproj b/Server/Shared/ServerShared.csproj new file mode 100644 index 0000000..9963929 --- /dev/null +++ b/Server/Shared/ServerShared.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + PKiK.Server.Shared + ..\..\.build\Server\bin\ + + + + + + + diff --git a/Shared/DTO/KeyDTO.cs b/Shared/DTO/KeyDTO.cs new file mode 100644 index 0000000..4dc807c --- /dev/null +++ b/Shared/DTO/KeyDTO.cs @@ -0,0 +1,14 @@ +namespace PKiK.Shared { + public class KeyDTO { + public int ID { get; set; } + public UserDTO User { get; set; } + public string PublicKey { get; set; } + public bool Active { get; set; } + + public KeyDTO() { + User = new UserDTO(); + PublicKey = ""; + Active = true; + } + } +} diff --git a/Shared/DTO/MessageDTO.cs b/Shared/DTO/MessageDTO.cs new file mode 100644 index 0000000..f689667 --- /dev/null +++ b/Shared/DTO/MessageDTO.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace PKiK.Shared { + public class MessageDTO { + public int ID { get; set; } + public UserDTO Sender { get; set; } + public List Recipients { get; set; } + public DateTime SentTime { get; set; } + + public MessageDTO() { + Sender = new UserDTO(); + Recipients = new List(); + } + + public OperationResult Validate() { + if(!Recipients.Any(x => x.User.ID == Sender.ID)) { //Check if the sender is one of the recipients + return new OperationResult(HttpStatusCode.BadRequest, "Sender is not on recipients list!"); + } + if(Recipients.Count < 2) { + return new OperationResult(HttpStatusCode.BadRequest, "Recipients list is not valid!"); + } + return new OperationResult(); + } + } +} diff --git a/Shared/DTO/RecipientDTO.cs b/Shared/DTO/RecipientDTO.cs new file mode 100644 index 0000000..abc2e01 --- /dev/null +++ b/Shared/DTO/RecipientDTO.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace PKiK.Shared { + public class RecipientDTO { + public int ID { get; set; } + public UserDTO User { get; set; } + public string Content { get; set; } + public DateTime? SeenTime { get; set; } //null if not seen + + public RecipientDTO() { + User = new UserDTO(); + SeenTime = null; + Content = string.Empty; + } + } +} diff --git a/Shared/DTO/UserDTO.cs b/Shared/DTO/UserDTO.cs new file mode 100644 index 0000000..ca0270c --- /dev/null +++ b/Shared/DTO/UserDTO.cs @@ -0,0 +1,19 @@ +namespace PKiK.Shared { + public class UserDTO { + public int ID { get; set; } + public string Username { get; set; } + public string? Name { get; set; } + public string? Surname { get; set; } + public string Password { get; set; } + + public UserDTO(string username) { + Username = username; + Password = string.Empty; + } + + public UserDTO() { + Username = string.Empty; + Password = string.Empty; + } + } +} diff --git a/Shared/Enums/EventType.cs b/Shared/Enums/EventType.cs new file mode 100644 index 0000000..118638e --- /dev/null +++ b/Shared/Enums/EventType.cs @@ -0,0 +1,8 @@ +namespace PKiK.Shared { + public enum EventType { + ERROR, + WARNING, + SUCCESS, + INFO + } +} diff --git a/Shared/OperationResult.cs b/Shared/OperationResult.cs new file mode 100644 index 0000000..024b35d --- /dev/null +++ b/Shared/OperationResult.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace PKiK.Shared { + public class OperationResult { + public bool IsSuccess { get; set; } + public HttpStatusCode HttpStatus { get; set; } + public object? Content { get; set; } + + public OperationResult(bool success = true) { + if(success) { + IsSuccess = true; + HttpStatus = HttpStatusCode.OK; + } else { + IsSuccess = false; + HttpStatus = HttpStatusCode.InternalServerError; + } + } + + public OperationResult(HttpStatusCode httpStatus) { + HttpStatus = httpStatus; + IsSuccess = ResponseValidator.IsSuccess(httpStatus); + } + + public OperationResult(HttpStatusCode httpStatus, object? content) { + IsSuccess = ResponseValidator.IsSuccess(httpStatus); + HttpStatus = httpStatus; + Content = content; + } + + public IActionResult GetResult() { + if (Content == null) { + return new StatusCodeResult((int)HttpStatus); + } else { + return new ObjectResult(Content) { StatusCode = (int)HttpStatus }; + } + } + } +} diff --git a/Shared/ResponseValidator.cs b/Shared/ResponseValidator.cs new file mode 100644 index 0000000..6447af3 --- /dev/null +++ b/Shared/ResponseValidator.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace PKiK.Shared { + public static class ResponseValidator { + public static bool IsSuccess(HttpStatusCode code) { + if ((int)code >= 200 && (int)code < 300) { + return true; + } + return false; + } + + public static bool IsSuccess(int code) { + if (code >= 200 && code < 300) { + return true; + } + return false; + } + + public static bool IsSuccess(int? code) { + if(code == null) { + return false; + } + if (code >= 200 && code < 300) { + return true; + } + return false; + } + + public static bool IsHttpResponseValid(IActionResult result) { + if(result is ObjectResult) + return IsSuccess(((ObjectResult)result).StatusCode); + if(result is StatusCodeResult) + return IsSuccess(((StatusCodeResult)result).StatusCode); + return false; + } + + public static int? GetStatusCode(IActionResult result) { + if (result is ObjectResult) + return ((ObjectResult)result).StatusCode; + if (result is StatusCodeResult) + return ((StatusCodeResult)result).StatusCode; + return null; + } + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj new file mode 100644 index 0000000..a55dac5 --- /dev/null +++ b/Shared/Shared.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + PKiK.Shared + ..\.build\Shared\bin\ + + + + + + + + + + + diff --git a/Tests/Server/DataGenerator.cs b/Tests/Server/DataGenerator.cs new file mode 100644 index 0000000..0357dad --- /dev/null +++ b/Tests/Server/DataGenerator.cs @@ -0,0 +1,31 @@ +namespace PKiK.Tests.Server { + internal static class DataGenerator { + private static string usernameChars = "qwertyuiopasdfghjklzxcvbnm"; + internal static string Username(int length = 5) { + var rng = new Random(); + string result = string.Empty; + for (int i = 0; i < length; i++) { + result += usernameChars.ElementAt(rng.Next(0, usernameChars.Length)); + } + return result; + } + + internal static UserDTO User() { + string random = Username(); + return new UserDTO() { + Username = random, + Name = random, + Surname = random, + Password = random + }; + } + + internal static List User(int count = 5) { + var list = new List(); + for(int i = 0; i < count; i++) { + list.Add(User()); + } + return list; + } + } +} diff --git a/Tests/Server/KeyServiceTest.cs b/Tests/Server/KeyServiceTest.cs new file mode 100644 index 0000000..5c020e4 --- /dev/null +++ b/Tests/Server/KeyServiceTest.cs @@ -0,0 +1,24 @@ +namespace PKiK.Tests.Server { + [TestClass] + public class KeyServiceTest { + [TestMethod] + public void RevokeKey() { + + } + + [TestMethod] + public void GetUserKey() { + + } + + [TestMethod] + public void AddKey() { + + } + + [TestMethod] + public void GetKey() { + + } + } +} diff --git a/Tests/Server/LoginServiceTest.cs b/Tests/Server/LoginServiceTest.cs new file mode 100644 index 0000000..5b6df05 --- /dev/null +++ b/Tests/Server/LoginServiceTest.cs @@ -0,0 +1,37 @@ +namespace PKiK.Tests.Server { + [TestClass] + public class LoginServiceTest { + private LoginService loginService = new LoginService(new AuthenticationService()); + private UserService userService = new UserService(); + private Config config = Mocker.MockConfig(); + + [TestMethod] + public void Register() { + var result = loginService.Register(DataGenerator.User()); + Assert.IsNotNull(result); + Assert.IsTrue(ResponseValidator.IsHttpResponseValid(result)); + } + + [TestMethod] + public void Login() { + var result = userService.GetUser("unittest"); + if(ResponseValidator.GetStatusCode(result) == 404) { + result = userService.AddUser(new UserDTO("unittest")); + Assert.IsTrue(ResponseValidator.IsHttpResponseValid(result)); + } + result = loginService.Login("unittest", "unittest"); + Assert.IsNotNull(result); + Assert.IsTrue(ResponseValidator.IsHttpResponseValid(result)); + } + + [TestMethod] + public void ValidateToken() { + + } + + [TestMethod] + public void RevokeToken() { + + } + } +} diff --git a/Tests/Server/MessageServiceTest.cs b/Tests/Server/MessageServiceTest.cs new file mode 100644 index 0000000..b6820fd --- /dev/null +++ b/Tests/Server/MessageServiceTest.cs @@ -0,0 +1,29 @@ +namespace PKiK.Tests.Server { + [TestClass] + public class MessageServiceTest { + [TestMethod] + public void Send() { + + } + + [TestMethod] + public void Delete() { + + } + + [TestMethod] + public void SetSeen() { + + } + + [TestMethod] + public void Get() { + + } + + [TestMethod] + public void GetForUser() { + + } + } +} diff --git a/Tests/Server/Mocker.cs b/Tests/Server/Mocker.cs new file mode 100644 index 0000000..ee8a62c --- /dev/null +++ b/Tests/Server/Mocker.cs @@ -0,0 +1,16 @@ +namespace PKiK.Tests.Server { + internal static class Mocker { + internal static Config MockConfig() { + var config = Config.Get(); + if(config.IsValid) { + return config; + } + config = Config.ReadConfig(); + if (config == null) { + throw new Exception("Cannot read config!"); + } + Config.Set(config); + return config; + } + } +} diff --git a/Tests/Server/ServerTests.csproj b/Tests/Server/ServerTests.csproj new file mode 100644 index 0000000..18bf730 --- /dev/null +++ b/Tests/Server/ServerTests.csproj @@ -0,0 +1,32 @@ + + + + net6.0 + enable + enable + + false + + ..\..\.build\Server\Tests\bin\ + + PKiK.Tests.Server + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Tests/Server/UserServiceTest.cs b/Tests/Server/UserServiceTest.cs new file mode 100644 index 0000000..4cb3e37 --- /dev/null +++ b/Tests/Server/UserServiceTest.cs @@ -0,0 +1,28 @@ +namespace PKiK.Tests.Server { + [TestClass] + public class UserServiceTest { + private UserService service = new UserService(); + private Config config = Mocker.MockConfig(); + + [TestMethod] + public void AddUser() { + var result = service.AddUser(DataGenerator.User()); + Assert.IsTrue(ResponseValidator.IsHttpResponseValid(result)); + } + + [TestMethod] + public void RemoveUser() { + + } + + [TestMethod] + public void ModifyUser() { + + } + + [TestMethod] + public void GetUser() { + + } + } +} diff --git a/Tests/Server/usings.cs b/Tests/Server/usings.cs new file mode 100644 index 0000000..23098ac --- /dev/null +++ b/Tests/Server/usings.cs @@ -0,0 +1,5 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Microsoft.AspNetCore.Mvc; +global using PKiK.Shared; +global using PKiK.Server.Shared; +global using PKiK.Server.Services; \ No newline at end of file