From f8ca47f3aadf241fc4fa06f23046224c36e72cb6 Mon Sep 17 00:00:00 2001
From: "jozsef.b@aycode.com" <9Rj@D}fVwBaN>
Date: Sun, 10 Dec 2023 14:13:19 +0100
Subject: [PATCH] DbContext, Dal, entities, etc..
---
AyCode.Core/AyCode.Core.csproj | 8 +
AyCode.Core/Consts/AcConst.cs | 104 +++++++++
AyCode.Core/Consts/AcEnv.cs | 22 ++
AyCode.Core/Consts/AcErrorCode.cs | 57 +++++
AyCode.Core/Consts/AcValidate.cs | 73 ++++++
AyCode.Core/Helpers/TaskHelper.cs | 70 ++++++
AyCode.Core/Logger/Logger.cs | 32 +++
.../AyCode.Database.Tests.csproj | 4 +
.../DatabaseTestModelBase.cs | 4 +-
AyCode.Database.Tests/DatabaseTests.cs | 4 +-
AyCode.Database.Tests/Users/UserDbSetTest.cs | 43 ++++
AyCode.Database/DalBase.cs | 34 ---
AyCode.Database/DataLayers/DalBase.cs | 82 +++++++
AyCode.Database/DataLayers/IDalBase.cs | 17 ++
AyCode.Database/DataLayers/PooledDal.cs | 64 ++++++
.../DataLayers/Users/UserDalBase.cs | 127 +++++++++++
.../DataLayers/Users/UserDalExtension.cs | 212 ++++++++++++++++++
.../DbContexts/Users/IUserDbContextBase.cs | 9 +
.../Users/IUserTokenDbContextBase.cs | 9 +
.../DbContexts/Users/UserDbContextBase.cs | 16 ++
AyCode.Database/DbSets/Users/IUserDbSet.cs | 10 +
.../DbSets/Users/IUserTokenDbSet.cs | 9 +
AyCode.Database/Extensions/DalExtension.cs | 39 ++++
.../Extensions/DbSessionExtension.cs | 41 ++++
.../Extensions/DbTransactionExtension.cs | 86 +++++++
AyCode.Entities/Users/UserBase.cs | 38 ++--
AyCode.Entities/Users/UserTokenBase.cs | 23 ++
AyCode.Interfaces/Users/IEmailAddress.cs | 13 ++
AyCode.Interfaces/Users/IPassword.cs | 6 +
AyCode.Interfaces/Users/IUserBase.cs | 4 +-
AyCode.Interfaces/Users/IUserTokenBase.cs | 16 ++
31 files changed, 1216 insertions(+), 60 deletions(-)
create mode 100644 AyCode.Core/Consts/AcConst.cs
create mode 100644 AyCode.Core/Consts/AcEnv.cs
create mode 100644 AyCode.Core/Consts/AcErrorCode.cs
create mode 100644 AyCode.Core/Consts/AcValidate.cs
create mode 100644 AyCode.Core/Helpers/TaskHelper.cs
create mode 100644 AyCode.Core/Logger/Logger.cs
create mode 100644 AyCode.Database.Tests/Users/UserDbSetTest.cs
delete mode 100644 AyCode.Database/DalBase.cs
create mode 100644 AyCode.Database/DataLayers/DalBase.cs
create mode 100644 AyCode.Database/DataLayers/IDalBase.cs
create mode 100644 AyCode.Database/DataLayers/PooledDal.cs
create mode 100644 AyCode.Database/DataLayers/Users/UserDalBase.cs
create mode 100644 AyCode.Database/DataLayers/Users/UserDalExtension.cs
create mode 100644 AyCode.Database/DbContexts/Users/IUserDbContextBase.cs
create mode 100644 AyCode.Database/DbContexts/Users/IUserTokenDbContextBase.cs
create mode 100644 AyCode.Database/DbContexts/Users/UserDbContextBase.cs
create mode 100644 AyCode.Database/DbSets/Users/IUserDbSet.cs
create mode 100644 AyCode.Database/DbSets/Users/IUserTokenDbSet.cs
create mode 100644 AyCode.Database/Extensions/DalExtension.cs
create mode 100644 AyCode.Database/Extensions/DbSessionExtension.cs
create mode 100644 AyCode.Database/Extensions/DbTransactionExtension.cs
create mode 100644 AyCode.Entities/Users/UserTokenBase.cs
create mode 100644 AyCode.Interfaces/Users/IEmailAddress.cs
create mode 100644 AyCode.Interfaces/Users/IPassword.cs
create mode 100644 AyCode.Interfaces/Users/IUserTokenBase.cs
diff --git a/AyCode.Core/AyCode.Core.csproj b/AyCode.Core/AyCode.Core.csproj
index 30402ac..a79a53c 100644
--- a/AyCode.Core/AyCode.Core.csproj
+++ b/AyCode.Core/AyCode.Core.csproj
@@ -6,4 +6,12 @@
enable
+
+
+
+
+
+
+
+
diff --git a/AyCode.Core/Consts/AcConst.cs b/AyCode.Core/Consts/AcConst.cs
new file mode 100644
index 0000000..2f9afd7
--- /dev/null
+++ b/AyCode.Core/Consts/AcConst.cs
@@ -0,0 +1,104 @@
+namespace AyCode.Core.Consts
+{
+ public static class AcConst
+ {
+ public const int ImageUploadLimit = 4096 * 1024;
+
+ public static int ConsentCount = 1;
+ public static readonly Guid NullMessageInstanceId;
+
+ public const int MinFullUserNameLength = 6;
+ public const int MaxFullUserNameLength = 250;
+
+ public const int MinUserNameLength = 3;
+ public const int MaxUserNameLength = 255;
+
+ public const int MinPlayerNameLength = MinUserNameLength;
+ public const int MaxPlayerNameLength = MaxUserNameLength;
+
+ public const int MinPasswordLength = 6;
+ public const int MaxPasswordLength = 32;
+ public const int MinUserTokenLength = 8;
+ public const int MaxUserTokenLength = 12;
+
+ public const int MinDomainLength = 6;
+ public const int MaxDomainLength = 100;
+
+ public const int MinDomainExtensionLength = 2;
+ public const int MaxDomainExtensionLength = 15;
+
+ public const int MaxTcpBodyLengthDefault = 65535;
+
+ public const string NullMessageString = "_";
+ public const string EmptyMessageString = "#";
+ public const long NullMessageDateTime = -1;
+
+ public static readonly string AnataBrandFolder;
+ public static readonly string AnataRootCacheFolder;
+ public static readonly string AnataUserMediaCacheFolder;
+ public static readonly string AnataAssetBundleCacheFolder;
+
+ public static readonly Guid RegionDefaultTemplateId;
+
+ public static readonly Guid InitScene;
+ public static readonly Guid LobbyRoom;
+
+ ///
+ /// MainScene, Lobby
+ ///
+ public static readonly Guid AnataCity;
+ public static readonly Guid PlayerGarage;
+ public static readonly Guid PlayerWardrobe;
+ public static readonly Guid LoadingId;
+ public static readonly Guid GlobalChat;
+
+ ///
+ /// RegionAdmin
+ ///
+ public static readonly Guid RegionAdminUserId;
+ public static readonly Guid RegionAdminPlayerId;
+
+ public static readonly Guid GamePlayer;
+ public static readonly Guid AdultPlayer;
+ public static readonly Guid BusinessPlayer;
+
+ public static readonly Guid AnataWorldDomainId;
+ public static readonly Guid AnataWorldBrandId;
+
+ public static string[] AvailableDomainSuffixes = new string[] { "3d", "anata", "app", "art", "club", "game", "blog", "shop", "biz", "chat", "conf", "city", "com", "net", "cool", "dance", "date", "fun", "design", "digital", "estate", "metaverse", "mv", "events", "fans", "fashion", "makeup", "fin", "fm", "sport", "gallery", "io", "info", "job", "mev", "land", "world", "life", "live", "lol", "love", "market", "media", "museum", "news", "ngo", "ninja", "kiwi", "one", "org", "party", "pink", "press", "slide", "property", "pub", "race", "sale", "school", "science", "social", "study", "style", "support", "tattoo", "team", "tech", "theatre", "town", "trade", "travel", "tv", "university", "education", "video", "vip", "vision", "wiki", "work", "xxx", "yeti", "ac", "ad", "ae", "af", "ag", "ai", "al", "am", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "su", "sv", "sx", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "ye", "yt", "za", "zm", "zw" };
+
+ static AcConst()
+ {
+ //var anataFolder = AcDomain.IsProductVersion ? "Anata" : "AnataDev";
+
+ //AnataRootCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AyCode", anataFolder);
+ //AnataUserMediaCacheFolder = Path.Combine(AnataRootCacheFolder, "MediaContents");
+ //AnataAssetBundleCacheFolder = Path.Combine(AnataRootCacheFolder, "AssetBundles");
+
+ //AnataBrandFolder = Path.Combine(AnataRootCacheFolder, "Brands");
+
+ //NullMessageInstanceId = Guid.Empty;
+
+ //RegionDefaultTemplateId = Guid.Parse(AntDomain.IsProductVersion ? "6201b570-09f2-4cce-93b8-0e92d37b9b6b" : "ac087b5a-1dff-48c0-ab7b-c3a5defa0d9e");
+
+ //InitScene = Guid.Parse("2db0be7b-5393-4b0f-a3f6-868155a6e62f");
+ //LobbyRoom = Guid.Parse("73a7dbc6-c85f-40f7-88d5-d7baecdbb961");
+ //AnataCity = Guid.Parse("dec9a00c-3fad-438d-aaa4-2109a7d5451f");
+ //LoadingId = Guid.Parse("3d7b7c8c-3eed-4625-a727-0caf665d4208");
+ //PlayerGarage = Guid.Parse("6ec1f5d7-57e9-440f-a486-20be5782f86a");
+ //PlayerWardrobe = Guid.Parse("69acf032-dc75-4fae-badb-1fe8473f664f");
+
+ //GlobalChat = Guid.Parse("ab5efc63-c654-41cd-b0a6-fa6aecc02d58");
+
+ //RegionAdminUserId = Guid.Parse("51B53FCC-CE85-473D-8E00-F42A7F0DBE1D");
+ //RegionAdminPlayerId = Guid.Parse("4ADF8015-C127-4E98-A022-B7407DBD3A38");
+
+ //GamePlayer = Guid.Parse("E023500F-D024-4D9F-A8F9-ECDF54382961");
+ //AdultPlayer = Guid.Parse("6A5E3C40-2022-4DFF-A362-396B73F15A69");
+ //BusinessPlayer = Guid.Parse("F73BFDFC-3A92-4A14-B09C-F5ED22BF4027");
+
+ //AnataWorldDomainId = Guid.Parse("8EFA53B3-7114-41E2-98F1-D690ECF509D2");
+ //AnataWorldBrandId = Guid.Parse("99265775-4B19-4975-B338-EB0F2F890806");
+ }
+ }
+}
diff --git a/AyCode.Core/Consts/AcEnv.cs b/AyCode.Core/Consts/AcEnv.cs
new file mode 100644
index 0000000..23e40cf
--- /dev/null
+++ b/AyCode.Core/Consts/AcEnv.cs
@@ -0,0 +1,22 @@
+//using Anata.Logger;
+
+namespace AyCode.Core.Consts
+{
+ //TODO: Adatbázisból kéne a környezeti beállításokat - J.
+ public static class AcEnv
+ {
+ ///
+ /// Environment.NewLine
+ ///
+ // ReSharper disable once InconsistentNaming
+ public static string NL => Environment.NewLine;
+
+ //public static AppModeType AppModeServer = AppModeType.Trace;
+ ////Elküldeni a kliens-nek, így szerver oldalról tudjuk szabályozni...
+ //public static AppModeType AppModeClient = AppModeType.Trace;
+
+ //public static LogLevel LogLevel = LogLevel.Detail;
+
+ public static ulong MaxLogItemsPerSession = 250;
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Core/Consts/AcErrorCode.cs b/AyCode.Core/Consts/AcErrorCode.cs
new file mode 100644
index 0000000..f8dda75
--- /dev/null
+++ b/AyCode.Core/Consts/AcErrorCode.cs
@@ -0,0 +1,57 @@
+namespace AyCode.Core.Consts
+{
+ public enum AcErrorCode : byte
+ {
+ Unset = 255,
+
+ //[DisplayName("Unknown error")]
+ UnknownError = 0,
+ InvalidMessage = 1,
+ SessionOpenFailed = 2,
+ ServerSideSessionOpenFailed = 3,
+ SessionNotFound = 4,
+ ServerSideSessionNotFound = 5,
+ DatabaseError = 6,
+ UserNameInUse = 7,
+ EmailInUse = 8,
+ UserAlreadyLoggedIn = 9,
+ WrongLoginData = 10,
+ UserWasntLoggedIn = 11,
+ NotAllowedMethod = 12,
+ EmailSendError = 13,
+ RoomFullOrHasStarted = 14,
+ UserIsLockOut = 15,
+ ClientVersionNotValid = 16,
+ PermissionTagDenied = 17,
+ RequestedGetItemNotfound = 18,
+ RequestedPostError = 19,
+ RequestedEditError = 20,
+ RequestedDeleteError = 21,
+ NotAllowedUserId = 22,
+ NotAllowedPlayerId = 23,
+ IdIsNullOrEmpty = 24,
+ IdNotFound = 25,
+ EntityIsNull = 26,
+ UserNameIsTooShort = 27,
+ PasswordIsTooShort = 28,
+ EmailFormatIsNotValid = 29,
+ UserTokenIsNotValid = 30,
+ ConsentNotAccepted = 31,
+ UserNameNotFound = 32,
+ PlayerNotFoundForUser = 33,
+ MarketItemExistsWithSameName = 34,
+ PlayerNameInUse = 35,
+ PlayerNameIsTooShort = 36,
+ ConfirmationPasswordNotValid = 37,
+
+ RegionPlayerMaxLimit = 38,
+ OwnerIsNotSpawnedInRegion = 39,
+ RegionDataNotFound = 40,
+ RegionNotFound = 41,
+
+ BrandIdNotFound = 42,
+ DomainIdNotFound = 43,
+
+ DisableAddUser = 44,
+ }
+}
diff --git a/AyCode.Core/Consts/AcValidate.cs b/AyCode.Core/Consts/AcValidate.cs
new file mode 100644
index 0000000..cd69019
--- /dev/null
+++ b/AyCode.Core/Consts/AcValidate.cs
@@ -0,0 +1,73 @@
+using AyCode.Utils.Extensions;
+using System.Text.RegularExpressions;
+
+namespace AyCode.Core.Consts
+{
+ public static class AcValidate
+ {
+ public static bool IsValidUserNameAndPasswordFormat(string username, string password, out AcErrorCode acErrorCode)
+ {
+ return IsValidUserNameFormat(username, out acErrorCode) && IsValidPasswordFormat(password, out acErrorCode);
+ }
+
+ public static bool IsValidUserNameFormat(string userName, out AcErrorCode acErrorCode)
+ {
+ acErrorCode = AcErrorCode.Unset;
+
+ var isValid = !userName.IsNullOrWhiteSpace() && userName.Length is >= AcConst.MinUserNameLength and <= AcConst.MaxUserNameLength && userName.Trim() == userName;
+
+ if (!isValid) acErrorCode = AcErrorCode.UserNameIsTooShort;
+ return isValid;
+ }
+
+ public static bool IsValidPlayerNameFormat(string playerName, out AcErrorCode acErrorCode)
+ {
+ acErrorCode = AcErrorCode.Unset;
+
+ var isValid = !playerName.IsNullOrWhiteSpace() && playerName.Length is >= AcConst.MinPlayerNameLength and <= AcConst.MaxPlayerNameLength && playerName.Trim() == playerName;
+
+ if (!isValid) acErrorCode = AcErrorCode.PlayerNameIsTooShort;
+ return isValid;
+ }
+
+ public static bool IsValidPasswordFormat(string password, out AcErrorCode acErrorCode)
+ {
+ acErrorCode = AcErrorCode.Unset;
+
+ var isValid = !password.IsNullOrWhiteSpace() && password.Length is >= AcConst.MinPasswordLength and <= AcConst.MaxPasswordLength;
+
+ if (!isValid) acErrorCode = AcErrorCode.PasswordIsTooShort;
+ return isValid;
+ }
+
+ public static bool IsValidEmailFormat(string email, out AcErrorCode acErrorCode)
+ {
+ acErrorCode = AcErrorCode.Unset;
+
+ //a@a.a -> length == 5
+ var isValid = !email.IsNullOrWhiteSpace() && email.Length >= 5 && email.Contains('@') && email.Contains('.') && !email.Contains(' ');
+
+ if (!isValid) acErrorCode = AcErrorCode.EmailFormatIsNotValid;
+ return isValid;
+ }
+
+ public static bool IsValidUserTokenFormat(string verificationToken, out AcErrorCode acErrorCode)
+ {
+ acErrorCode = AcErrorCode.Unset;
+
+ var isValid = !verificationToken.IsNullOrWhiteSpace() && verificationToken.Length is >= AcConst.MinUserTokenLength and <= AcConst.MaxUserTokenLength;
+
+ if (!isValid) acErrorCode = AcErrorCode.UserTokenIsNotValid;
+ return isValid;
+ }
+
+ public static bool IsValidDomain(string domainName)
+ {
+ var suffixes = string.Join('|', AcConst.AvailableDomainSuffixes);
+ var regexp = new Regex("^[a-z0-9]{3,90}\\.(" + suffixes + ")$"); // a pattern-t vegyük ki konstansba a dll-re - B.
+ var match = regexp.IsMatch(domainName);
+
+ return match;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Core/Helpers/TaskHelper.cs b/AyCode.Core/Helpers/TaskHelper.cs
new file mode 100644
index 0000000..8e14efe
--- /dev/null
+++ b/AyCode.Core/Helpers/TaskHelper.cs
@@ -0,0 +1,70 @@
+namespace AyCode.Core.Helpers
+{
+ public static class TaskHelper
+ {
+ public static bool WaitTo(Func predicate, int msTimeout = 10000, int msDelay = 5)
+ => WaitToAsync(predicate, msTimeout, msDelay).GetAwaiter().GetResult();
+
+ public static Task WaitToAsync(Func predicate, int msTimeout = 10000, int msDelay = 5)
+ {
+ return ToThreadPoolTask(async () =>
+ {
+ var result = false;
+ var dtTimeout = DateTime.UtcNow.AddMilliseconds(msTimeout).Ticks;
+
+ while (dtTimeout > DateTime.UtcNow.Ticks && !(result = predicate()))
+ await Task.Delay(msDelay).ConfigureAwait(false); //Thread.Sleep(msDelay);
+
+ return result;
+ });
+ }
+
+ public static void Forget(this Task task)
+ {
+ if (!task.IsCompleted || task.IsFaulted)
+ _ = ForgetAwaited(task);
+
+ static async Task ForgetAwaited(Task task)
+ {
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await Task.FromException(ex).ConfigureAwait(true);
+ }
+ }
+ }
+
+ //public static void Forget(this ValueTask task)
+ //{
+ // if (!task.IsCompleted || task.IsFaulted)
+ // _ = ForgetAwaited(task);
+
+ // static async ValueTask ForgetAwaited(ValueTask task)
+ // {
+ // try
+ // {
+ // await task.ConfigureAwait(false);
+ // }
+ // catch (Exception ex)
+ // {
+ // //TODO: .net5, .net6 feature! - J.
+ // ValueTask.FromException(ex).ConfigureAwait(true);
+ // }
+ // }
+ //}
+
+ //TODO: Cancellation token params - J.
+ //public static void RunOnThreadPool(this Task task) => Task.Run(() => _ = task).Forget(); //TODO: Letesztelni, a ThreadId-kat! - J.
+ public static void RunOnThreadPool(this Action action) => ToThreadPoolTask(action).Forget();
+ public static void RunOnThreadPool(this Func func) => ToThreadPoolTask(func).Forget();
+ public static Task ToThreadPoolTask(this Action action) => Task.Run(action);
+ //public static Task ToThreadPoolTask(this Task task) => Task.Run(() => _ = task);
+ //public static void ToParallelTaskStart(this Task task) => task.Start();
+ //public static Task ToThreadPoolTask(this Task task) => Task.Run(() => task);
+ public static Task ToThreadPoolTask(this Func> func) => Task.Run(func); //TODO: Letesztelni, a ThreadId-kat! - J.
+ public static Task ToThreadPoolTask(this Func func) => Task.Run(func);
+ }
+}
diff --git a/AyCode.Core/Logger/Logger.cs b/AyCode.Core/Logger/Logger.cs
new file mode 100644
index 0000000..7d6ce6d
--- /dev/null
+++ b/AyCode.Core/Logger/Logger.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AyCode.Core.Consts;
+
+namespace AyCode.Core.Logger
+{
+ public static class Logger
+ {
+ public static void Info(string text)
+ {
+ Console.WriteLine(text);
+ }
+
+ public static void Warning(string text)
+ {
+ Console.WriteLine(text);
+ }
+
+ public static void Error(string text)
+ {
+ Console.WriteLine(text);
+ }
+
+ public static void Error(string text, Exception ex)
+ {
+ Console.WriteLine($"{text}{AcEnv.NL}{ex.Message}{AcEnv.NL}{ex}");
+ }
+ }
+}
diff --git a/AyCode.Database.Tests/AyCode.Database.Tests.csproj b/AyCode.Database.Tests/AyCode.Database.Tests.csproj
index 9129835..1ebdf29 100644
--- a/AyCode.Database.Tests/AyCode.Database.Tests.csproj
+++ b/AyCode.Database.Tests/AyCode.Database.Tests.csproj
@@ -30,4 +30,8 @@
+
+
+
+
diff --git a/AyCode.Database.Tests/DatabaseTestModelBase.cs b/AyCode.Database.Tests/DatabaseTestModelBase.cs
index b81c0c1..b04328e 100644
--- a/AyCode.Database.Tests/DatabaseTestModelBase.cs
+++ b/AyCode.Database.Tests/DatabaseTestModelBase.cs
@@ -1,10 +1,10 @@
using AyCode.Core.Tests;
+using AyCode.Database.DataLayers;
using AyCode.Database.DbContexts;
namespace AyCode.Database.Tests;
-//public abstract class DatabaseTestModelBase : TestModelBase where TDal : DbContextBase
-public abstract class DatabaseTestModelBase : TestModelBase
+public abstract class DatabaseTestModelBase : TestModelBase where TDbContext : DbContextBase
{
protected DatabaseTestModelBase()
{
diff --git a/AyCode.Database.Tests/DatabaseTests.cs b/AyCode.Database.Tests/DatabaseTests.cs
index f4ace3d..0b8ad84 100644
--- a/AyCode.Database.Tests/DatabaseTests.cs
+++ b/AyCode.Database.Tests/DatabaseTests.cs
@@ -1,7 +1,9 @@
+using AyCode.Database.DbContexts;
+
namespace AyCode.Database.Tests
{
[TestClass]
- public abstract class DatabaseTests : DatabaseTestModelBase
+ public abstract class DatabaseTests : DatabaseTestModelBase
{
[TestMethod]
public void TestMethod1()
diff --git a/AyCode.Database.Tests/Users/UserDbSetTest.cs b/AyCode.Database.Tests/Users/UserDbSetTest.cs
new file mode 100644
index 0000000..357c275
--- /dev/null
+++ b/AyCode.Database.Tests/Users/UserDbSetTest.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AyCode.Database.DbContexts;
+using AyCode.Database.DbContexts.Users;
+using AyCode.Interfaces.Users;
+
+namespace AyCode.Database.Tests.Users
+{
+ [TestClass]
+ public abstract class UserDbSetTest : DatabaseTestModelBase
+ where TDbContext : DbContextBase, IUserDbContextBase
+ where TUser : class, IUserBase
+ where TUserToken : class, IUserTokenBase
+
+ {
+ //UserDalBase
+ //private UserDal _userDal;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ }
+
+ [TestCleanup]
+ public void TearDown()
+ {
+ }
+
+ //[TestMethod]
+ //[DataRow("test@tiam.hu")]
+ //public void GetUserByEmail_ReturnsUser_WhenUserExists(string email)
+ //{
+ // //var userDal = PooledDal.CreateDal();
+ // var user = _userDal.GetUserByEmail(email);
+
+ // Assert.IsNotNull(user);
+ // Assert.AreEqual(email, user.EmailAddress);
+ //}
+ }
+}
diff --git a/AyCode.Database/DalBase.cs b/AyCode.Database/DalBase.cs
deleted file mode 100644
index 1f1d22a..0000000
--- a/AyCode.Database/DalBase.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using AyCode.Database.DbContexts;
-using AyCode.Utils.Extensions;
-
-namespace AyCode.Database;
-
-public class DalBase where TDbContext : DbContextBase
-{
- public string Name { get; private set; }
- private TDbContext _ctx;
- public TDbContext Ctx => _ctx;
-
- public DalBase() : this(Activator.CreateInstance())
- { }
-
- public DalBase(TDbContext ctx)
- {
- _ctx = ctx;
-
- if (_ctx.Name.IsNullOrWhiteSpace())
- _ctx.Name = _ctx.GetType().Name;
-
- Name = $"{this.GetType().Name}, {_ctx.Name}";
- }
-
- public override string ToString()
- {
- return Name;
- }
-}
\ No newline at end of file
diff --git a/AyCode.Database/DataLayers/DalBase.cs b/AyCode.Database/DataLayers/DalBase.cs
new file mode 100644
index 0000000..80deac0
--- /dev/null
+++ b/AyCode.Database/DataLayers/DalBase.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AyCode.Core.Helpers;
+using AyCode.Database.DbContexts;
+using AyCode.Database.Extensions;
+using AyCode.Interfaces.Entities;
+using AyCode.Utils.Extensions;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DataLayers;
+
+public abstract class DalBase : IDalBase where TDbContext : DbContextBase
+{
+ public string Name { get; private set; }
+
+ public bool AutoCloseSession { get; set; }
+
+ public TDbContext Context { get; private set; }
+
+ //protected DalBase()
+ //{
+ // Name = $"{GetType().Name}";
+ //}
+
+ protected DalBase() : this(Activator.CreateInstance())
+ { }
+
+ protected DalBase(TDbContext ctx)
+ {
+ Context = ctx;
+
+ if (Context.Name.IsNullOrWhiteSpace())
+ Context.Name = Context.GetType().Name;
+
+ Name = $"{GetType().Name}, {Context.Name}";
+ }
+
+ public TDbContext CreateDbContext()
+ {
+ return Activator.CreateInstance();
+ }
+
+ public void CloseDbContext(ref TDbContext? ctx)
+ {
+ ctx?.Dispose();
+ ctx = null;
+ }
+
+ public Task SessionAsync(Func callback)
+ => this.SessionAsync(callback);
+
+ public Task> SessionAsync(Func> callback) where TEntity : IEntity
+ => this.SessionAsync(callback);
+
+ public TResultType Session(Func callback)
+ => this.Session(callback);
+
+ public IEnumerable Session(Func> callback) where TEntity : IEntity
+ => this.Session(callback);
+
+ public Task TransactionAsync(Func callbackTransactionBody, bool throwException = false)
+ => this.TransactionAsync(callbackTransactionBody, throwException);
+
+ public bool Transaction(Func callbackTransactionBody, bool throwException = false)
+ => this.Transaction(callbackTransactionBody, throwException);
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ //public void Dispose()
+ //{
+ // Ctx?.Dispose();
+ // Ctx = null;
+
+ // //CloseDbContext(ref Ctx);
+ //}
+}
\ No newline at end of file
diff --git a/AyCode.Database/DataLayers/IDalBase.cs b/AyCode.Database/DataLayers/IDalBase.cs
new file mode 100644
index 0000000..b30f3f3
--- /dev/null
+++ b/AyCode.Database/DataLayers/IDalBase.cs
@@ -0,0 +1,17 @@
+using AyCode.Database.DbContexts;
+
+namespace AyCode.Database.DataLayers;
+
+public interface IDalBase //: IDisposable
+{
+ public string Name { get; }
+}
+
+public interface IDalBase : IDalBase where TDbContext : DbContextBase
+{
+ public bool AutoCloseSession { get; set; }
+ public TDbContext Context { get; }
+
+ public TDbContext CreateDbContext();
+ public void CloseDbContext(ref TDbContext? ctx);
+}
\ No newline at end of file
diff --git a/AyCode.Database/DataLayers/PooledDal.cs b/AyCode.Database/DataLayers/PooledDal.cs
new file mode 100644
index 0000000..11ffc2b
--- /dev/null
+++ b/AyCode.Database/DataLayers/PooledDal.cs
@@ -0,0 +1,64 @@
+using System.Collections.Concurrent;
+using AyCode.Utils.Extensions;
+
+namespace AyCode.Database.DataLayers
+{
+ public sealed class PooledDal
+ {
+ private static readonly PooledDal Instance = new();
+
+ private readonly ConcurrentDictionary> _dataLayersPoolById = new();
+ private static ConcurrentDictionary> DataLayersPoolById => Instance._dataLayersPoolById;
+
+ public static int Count => DataLayersPoolById.Count;
+ public static void Clear() => DataLayersPoolById.Clear();
+
+ public static TDal CreateDal() where TDal : IDalBase => Activator.CreateInstance();
+
+ ///
+ ///
+ ///
+ ///
+ /// ONLY SessionId or logged in PlayerId!
+ ///
+ public static TDal GetDalById(Guid id) where TDal : IDalBase
+ {
+ if (id.IsNullOrEmpty()) return CreateDal(); //TODO: A Guid.Empty id-t ÁTGONDOLNI! - J.
+
+ if (DataLayersPoolById.TryGetValue(id, out var dataLayersByType))
+ return GetDalByType(dataLayersByType);
+
+ dataLayersByType = new ConcurrentDictionary();
+ DataLayersPoolById.TryAdd(id, dataLayersByType);
+
+ return GetDalByType(dataLayersByType);
+ }
+
+ private static TDal GetDalByType(ConcurrentDictionary dataLayersByType) where TDal : IDalBase
+ {
+ if (dataLayersByType.TryGetValue(typeof(TDal), out var dataLayer))
+ return (TDal)dataLayer;
+
+ var resultDal = CreateDal();
+ dataLayersByType.TryAdd(typeof(TDal), resultDal);
+
+ return resultDal;
+ }
+
+ ///
+ ///
+ ///
+ /// SessionId or PlayerId
+ ///
+ public static bool Remove(Guid id)//, bool keepGuidEmptyId = true)
+ {
+ //if (keepGuidEmptyId && id == Guid.Empty) return false;
+
+ if (!DataLayersPoolById.TryRemove(id, out var dataLayersByType))
+ return false;
+
+ dataLayersByType.Clear();
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Database/DataLayers/Users/UserDalBase.cs b/AyCode.Database/DataLayers/Users/UserDalBase.cs
new file mode 100644
index 0000000..240c70e
--- /dev/null
+++ b/AyCode.Database/DataLayers/Users/UserDalBase.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AyCode.Database.DbContexts;
+using AyCode.Database.DbContexts.Users;
+using AyCode.Entities.Users;
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+using AyCode.Database.Extensions;
+using AyCode.Core.Consts;
+
+namespace AyCode.Database.DataLayers.Users
+{
+ public abstract class UserDalBase : DalBase, IUserDbContextBase
+ where TDbContext : DbContextBase, IUserDbContextBase
+ where TUser : class, IUserBase
+ where TUserToken : class, IUserTokenBase
+ {
+ public DbSet Users { get; set; }
+ public DbSet UserTokens { get; set; }
+
+ public TUser? GetUserById(Guid userId) => Session(x => x.GetUserById(userId));
+ public Task GetUserByIdAsync(Guid userId) => SessionAsync(x => x.GetUserById(userId));
+
+ public TUser? GetUserByEmail(string email) => Session(x => x.GetUserByEmail(email));
+ public Task GetUserByEmailAsync(string email) => SessionAsync(x => x.GetUserByEmail(email));
+
+ //public UserToken CreateUserToken(Guid userId, string verificationToken)
+ //{
+ // UserToken userToken = null;
+ // Transaction(ctx =>
+ // {
+ // userToken = ctx.CreateUserToken(userId, verificationToken);
+ // return userToken != null;
+ // }
+ // );
+
+ // return userToken;
+ //}
+
+ //public UserToken UpdateUserTokenSent(Guid userId, string verificationToken, DateTime tokenSentUtc)
+ //{
+ // UserToken userToken = null;
+ // Transaction(ctx =>
+ // {
+ // userToken = ctx.UpdateUserTokenSent(userId, verificationToken, tokenSentUtc);
+ // return userToken != null;
+ // }
+ // );
+
+ // return userToken;
+ //}
+
+ //public UserToken GetActiveUserToken(Guid userId)
+ // => Session(ctx => ctx.GetActiveUserToken(userId));
+
+ //public UserToken GetUserToken(Guid userId, string verificationToken)
+ // => Session(ctx => ctx.GetUserToken(userId, verificationToken));
+
+ //public bool IsValidToken(Guid userId, string verificationToken)
+ // => Transaction(ctx => ctx.IsValidToken(userId, verificationToken));
+
+ //public bool IsValidToken(string email, string verificationToken)
+ //{
+ // return Transaction(ctx =>
+ // {
+ // var user = ctx.GetUserByEmail(email);
+
+ // return user != null && ctx.IsValidToken(user.Id, verificationToken);
+ // });
+ //}
+
+ //public void DeactivateTokens(Guid userId)
+ //{
+ // Transaction(ctx =>
+ // {
+ // ctx.DeactivateTokens(userId);
+ // return true;
+ // });
+ //}
+ //public string GetValidVerificationToken(UserDal userDal, Guid userId, out UserToken userToken)
+ //{
+ // string verificationToken;
+ // userToken = userDal.GetActiveUserToken(userId);
+
+ // if (userToken == null)
+ // {
+ // verificationToken = AntGenerator.NewToken();
+ // userToken = userDal.CreateUserToken(userId, verificationToken);
+ // }
+ // else verificationToken = userToken.Token;
+
+ // return verificationToken;
+ //}
+
+ public AcErrorCode GetUserForEmailValidation(string email, Func callback)
+ {
+ TUser? user = null;
+ var errorCodes = AcErrorCode.Unset;
+
+ Transaction(ctx =>
+ {
+ var lowerEmail= email.ToLower();
+
+ user = ctx.Users.FirstOrDefault(x => x.EmailAddress == lowerEmail && x.EmailConfirmed);
+
+ if (user == null)
+ {
+ errorCodes = AcErrorCode.UserNameNotFound;
+ return false;
+ }
+
+ //user.Profile = ctx.Profiles.FirstOrDefault(x => x.OwnerId == user.Id);
+
+ //if (!ctx.IsUserConsentsAccepted(user.Id)) errorCodes = AcErrorCode.ConsentNotAccepted;
+
+ return true;
+ });
+
+ //if (user != null)
+
+ return callback(user, errorCodes);
+ }
+ }
+}
diff --git a/AyCode.Database/DataLayers/Users/UserDalExtension.cs b/AyCode.Database/DataLayers/Users/UserDalExtension.cs
new file mode 100644
index 0000000..f32a37c
--- /dev/null
+++ b/AyCode.Database/DataLayers/Users/UserDalExtension.cs
@@ -0,0 +1,212 @@
+using System.Diagnostics;
+using AyCode.Core;
+using AyCode.Database.DbContexts;
+using AyCode.Database.DbContexts.Users;
+using AyCode.Database.DbSets.Users;
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AyCode.Database.DataLayers.Users
+{
+ public static class UserDalExtension
+ {
+ //public static bool DeleteUser(this IUserDbContextBase ctx, IUserBase user)
+ //{
+ // if (user == null) return false;
+
+ // var players = ctx.Players.Where(x => x.OwnerId == user.Id).ToList();
+
+ // foreach (var player in players)
+ // {
+ // ctx.Profiles.RemoveRange(ctx.Profiles.Where(x => x.OwnerId == player.PlayerId || x.OwnerId == user.Id));
+ // ctx.Wallets.RemoveRange(ctx.Wallets.Where(x => x.OwnerId == player.PlayerId || x.OwnerId == user.Id));
+
+ // ctx.Sessions.RemoveRange(ctx.Sessions.Where(x => x.PlayerId == player.PlayerId));
+ // //ctx.SessionDetails.RemoveRange(ctx.SessionDetails.Where(x => x.PlayerId == player.PlayerId));
+
+ // ctx.PlayerPermissionTags.RemoveRange(player.PermissionTags);
+ // }
+
+ // ctx.Players.RemoveRange(players);
+ // ctx.UserTokens.RemoveRange(ctx.UserTokens.Where(x => x.UserId == user.Id));
+ // //ctx.PermissionCategory.RemoveRange(ctx.PermissionCategory.Where(x => x.ForeignKey == user.Id));
+
+ // ctx.SaveChanges(); //A Player constraint miatt kell, különben nem engedi törölni a User-t! - J.
+ // return ctx.Remove(user).State == EntityState.Deleted;
+ //}
+
+ public static TUser? GetUserById(this IUserDbSet ctx, Guid userId) where TUser : class, IUserBase
+ {
+ return ctx.Users.FirstOrDefault(u => u.Id == userId);
+ }
+
+ public static TUser? GetUserByEmail(this IUserDbSet ctx, string email) where TUser : class, IUserBase
+ {
+ var emailLower = email.ToLower();
+ return ctx.Users.FirstOrDefault(u => u.EmailAddress == emailLower);
+ }
+
+ //public static IEnumerable GetUserConsentsByUserId(this UserDbContextBase ctx, Guid userId)
+ // => ctx.UserToUserConsents.Where(u => u.UserId == userId);
+
+ //public static bool IsUserConsentsAccepted(this UserDbContextBase ctx, Guid userId)
+ // => ctx.GetUserConsentsByUserId(userId).Count(x => x.IsAccepted) == AcConst.ConsentCount;
+
+ //public static bool SetUserConsentsToAccepted(this UserDbContextBase ctx, Guid userId)
+ //{
+ // var user = ctx.GetUserById(userId);
+ // return ctx.SetUserConsentsToAccepted(user);
+ //}
+
+ //public static bool SetUserConsentsToAccepted(this UserDbContextBase ctx, IUserBase? user)
+ //{
+ // if (!user.EmailConfirmed) return false; //Ez nem kéne ide... - J.
+
+ // var hashSet = ctx.GetUserConsentsByUserId(user.Id).Select(x => x.UserConsentId).ToHashSet();
+ // var userConsents = ctx.UserConsents.Where(x => !hashSet.Contains(x.Id));
+
+ // ctx.AddRange(userConsents.Select(userConsent => new UserToUserConsent(user.Id, userConsent.Id, true)));
+
+ // var userToUserConsents = ctx.GetUserConsentsByUserId(user.Id).Where(x => !x.IsAccepted).ToList();
+ // foreach (var userToUserConsent in userToUserConsents)
+ // {
+ // userToUserConsent.IsAccepted = true;
+ // }
+
+ // ctx.UpdateRange(userToUserConsents);
+
+ // return true;
+ //}
+
+
+ public static bool ChangePassword(this IUserDbContextBase ctx, TUser? user, string passwordHash, string verificationToken)
+ where TUser : class, IUserBase
+ where TUserToken : class, IUserTokenBase
+ {
+ if (user == null || !ctx.IsValidToken(user.Id, verificationToken)) return false;
+
+ user.Password = passwordHash;
+ ctx.Users.Update(user);
+
+ ctx.DeactivateTokens(user.Id);
+
+ return true;
+ }
+
+ public static TUserToken? UpdateUserTokenSent(this IUserTokenDbSet ctx, Guid userId, string verificationToken, DateTime tokenSentUtc) where TUserToken : class, IUserTokenBase
+ {
+ var userToken = ctx.GetUserToken(userId, verificationToken);
+
+ if (userToken == null) return null;
+
+ userToken.TokenSent = tokenSentUtc;
+ userToken.TokenExpiration ??= tokenSentUtc.AddDays(1);
+
+ var utcNow = DateTime.UtcNow;
+ userToken.IsActive = tokenSentUtc <= utcNow && userToken.TokenExpiration > utcNow;
+
+ ctx.UserTokens.Update(userToken);
+ return userToken;
+ }
+
+ public static TUserToken CreateUserToken(this IUserTokenDbSet ctx, Guid userId, string verificationToken) where TUserToken : class, IUserTokenBase
+ {
+ var userToken = Activator.CreateInstance();
+ userToken.UserId = userId;
+ userToken.Token = verificationToken;
+
+ return ctx.CreateUserToken(userToken);
+ }
+
+ public static TUserToken CreateUserToken(this IUserTokenDbSet ctx, TUserToken userToken) where TUserToken : class, IUserTokenBase
+ {
+ ctx.DeactivateTokens(userToken.UserId);
+
+ userToken.IsActive = true;
+ ctx.UserTokens.Add(userToken);
+
+ return userToken;
+ }
+
+ public static TUserToken? GetActiveUserToken(this IUserTokenDbSet ctx, Guid userId) where TUserToken : class, IUserTokenBase
+ => ctx.UserTokens.SingleOrDefault(x => x.UserId == userId && x.IsActive && (x.TokenExpiration == null || x.TokenExpiration > DateTime.UtcNow));
+
+ public static TUserToken? GetUserToken(this IUserTokenDbSet ctx, Guid userId, string verificationToken) where TUserToken : class?, IUserTokenBase?
+ => ctx.UserTokens.SingleOrDefault(x => x.UserId == userId && x.Token == verificationToken);
+
+ public static bool IsValidToken(this IUserTokenDbSet ctx, Guid userId, string verificationToken) where TUserToken : class, IUserTokenBase
+ => ctx.UserTokens.Any(x => x.UserId == userId && x.IsActive && x.Token == verificationToken && x.TokenExpiration > DateTime.UtcNow && x.TokenSent < DateTime.UtcNow);
+
+ public static void DeactivateTokens(this IUserTokenDbSet ctx, Guid userId) where TUserToken : class, IUserTokenBase
+ {
+ var activeUserTokens = ctx.UserTokens.Where(x => x.UserId == userId && x.IsActive);
+
+ foreach (var activeUserToken in activeUserTokens)
+ activeUserToken.IsActive = false;
+
+ ctx.UserTokens.UpdateRange(activeUserTokens);
+ }
+
+ ////public static void GetUserDetails(this IUserDal userDal, Guid userId, Action callback)
+ //// => callback(userDal.Get(userId));
+
+ //public static void UsernameAndEmailAvailable(this IUserDal userDal, string username, string email, Action callback)
+ //{
+ // IUserBase notConfirmedUser = null;
+
+ // var lowerEmail = email.ToLower();
+ // var lowerUserName = username.ToLower();
+
+ // var isAvailable = AcValidate.IsValidEmailFormat(lowerEmail, out var resultErrorCode) && AcValidate.IsValidUserNameFormat(lowerUserName, out resultErrorCode);
+
+ // if (isAvailable)
+ // {
+ // //userDal.RowCount(ctx => ctx.EmailConfirmed && (ctx.NormalizedUserName == lowerUserName || ctx.NormalizedEmail == lowerEmail)) == 0
+
+ // var users = userDal.Where(ctx => ctx.NormalizedUserName == lowerUserName || ctx.NormalizedEmail == lowerEmail).ToList();
+
+ // notConfirmedUser = users.FirstOrDefault(x => !x.EmailConfirmed && x.NormalizedEmail == lowerEmail);
+
+ // //isAvailable = (users.Count == 0 || (users.Count == 1 && notConfirmedUser != null));
+
+ // if (users.Count > 1) resultErrorCode = AcErrorCode.EmailInUse;
+ // else if (users.Count == 1 && notConfirmedUser == null)
+ // {
+ // resultErrorCode = users.Any(x => x.NormalizedEmail == lowerEmail) ? AcErrorCode.EmailInUse : AcErrorCode.UserNameInUse;
+ // }
+
+ // isAvailable = resultErrorCode == AcErrorCode.Unset;
+ // }
+
+ // callback(isAvailable, resultErrorCode, notConfirmedUser);
+ //}
+
+
+ //public static void AddNewUser(this IUserDal userDal, IUserBase newUser, Action callback)
+ //{
+ // userDal.Add(newUser);
+ // callback(newUser);
+ //}
+
+ //public static void AddNewUser(this IUserDal userDal, Guid userId, string username, string email, string password, Action callback)
+ //{
+ // var newUser = new AspNetUser(userId, username, email, password);
+ // userDal.Add(newUser);
+
+ // callback();
+ //}
+
+ ////public static void DeleteUser(this IUserDal userDal, string username, Action callback)
+ ////{
+ //// userDal.Session(ctx =>
+ //// {
+ //// var user = ctx.Users.FirstOrDefault(u => u.NormalizedUserName == username.ToUpperInvariant());
+ //// if (user == null) return;
+
+ //// ctx.Set().Delete(user);
+ //// ctx.SaveChanges();
+ //// };
+ ////}
+ }
+}
diff --git a/AyCode.Database/DbContexts/Users/IUserDbContextBase.cs b/AyCode.Database/DbContexts/Users/IUserDbContextBase.cs
new file mode 100644
index 0000000..fb29b3c
--- /dev/null
+++ b/AyCode.Database/DbContexts/Users/IUserDbContextBase.cs
@@ -0,0 +1,9 @@
+using AyCode.Database.DbSets.Users;
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DbContexts.Users;
+
+public interface IUserDbContextBase : IUserDbSet, IUserTokenDbSet where TUser : class, IUserBase where TUserToken : class, IUserTokenBase
+{
+}
\ No newline at end of file
diff --git a/AyCode.Database/DbContexts/Users/IUserTokenDbContextBase.cs b/AyCode.Database/DbContexts/Users/IUserTokenDbContextBase.cs
new file mode 100644
index 0000000..1d9bde6
--- /dev/null
+++ b/AyCode.Database/DbContexts/Users/IUserTokenDbContextBase.cs
@@ -0,0 +1,9 @@
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DbContexts.Users;
+
+public interface IUserTokenDbContextBase where TUserToken : class, IUserTokenBase
+{
+ DbSet UserToken { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Database/DbContexts/Users/UserDbContextBase.cs b/AyCode.Database/DbContexts/Users/UserDbContextBase.cs
new file mode 100644
index 0000000..d6c9c32
--- /dev/null
+++ b/AyCode.Database/DbContexts/Users/UserDbContextBase.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DbContexts.Users
+{
+ public abstract class UserDbContextBase : DbContextBase, IUserDbContextBase where TUser : class, IUserBase where TUserToken : class, IUserTokenBase
+ {
+ public required DbSet Users { get; set; }
+ public required DbSet UserTokens { get; set; }
+ }
+}
diff --git a/AyCode.Database/DbSets/Users/IUserDbSet.cs b/AyCode.Database/DbSets/Users/IUserDbSet.cs
new file mode 100644
index 0000000..ef7fd12
--- /dev/null
+++ b/AyCode.Database/DbSets/Users/IUserDbSet.cs
@@ -0,0 +1,10 @@
+using AyCode.Database.DbContexts;
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DbSets.Users;
+
+public interface IUserDbSet where TUser : class, IUserBase
+{
+ DbSet Users { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Database/DbSets/Users/IUserTokenDbSet.cs b/AyCode.Database/DbSets/Users/IUserTokenDbSet.cs
new file mode 100644
index 0000000..35118c1
--- /dev/null
+++ b/AyCode.Database/DbSets/Users/IUserTokenDbSet.cs
@@ -0,0 +1,9 @@
+using AyCode.Interfaces.Users;
+using Microsoft.EntityFrameworkCore;
+
+namespace AyCode.Database.DbSets.Users;
+
+public interface IUserTokenDbSet where TUserToken : class, IUserTokenBase
+{
+ DbSet UserTokens { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Database/Extensions/DalExtension.cs b/AyCode.Database/Extensions/DalExtension.cs
new file mode 100644
index 0000000..8532f4e
--- /dev/null
+++ b/AyCode.Database/Extensions/DalExtension.cs
@@ -0,0 +1,39 @@
+using AyCode.Core.Helpers;
+using AyCode.Database.DataLayers;
+using AyCode.Database.DbContexts;
+using AyCode.Interfaces.Entities;
+
+namespace AyCode.Database.Extensions;
+
+public static class DalExtension
+{
+ public static Task SessionAsync(this IDalBase dal, Func callback) where TDbContext : DbContextBase
+ => TaskHelper.ToThreadPoolTask(() => dal.Session(callback));
+
+ public static Task> SessionAsync(this IDalBase dal, Func> callback) where TEntity : IEntity where TDbContext : DbContextBase
+ => TaskHelper.ToThreadPoolTask(() => dal.Session(callback));
+
+ public static TResultType Session(this IDalBase dal, Func callback) where TDbContext : DbContextBase
+ {
+ using var ctx = dal.CreateDbContext();
+
+ return ctx.Session(callback);
+ }
+
+ public static IEnumerable Session(this IDalBase dal, Func> callback) where TEntity : IEntity where TDbContext : DbContextBase
+ {
+ using var ctx = dal.CreateDbContext();
+
+ return ctx.Session(callback);
+ }
+
+ public static Task TransactionAsync(this IDalBase dal, Func callbackTransactionBody, bool throwException = false) where TDbContext : DbContextBase
+ => TaskHelper.ToThreadPoolTask(() => dal.Transaction(callbackTransactionBody, throwException));
+
+ public static bool Transaction(this IDalBase dal, Func callbackTransactionBody, bool throwException = false) where TDbContext : DbContextBase
+ {
+ using var ctx = dal.CreateDbContext();
+
+ return ctx.Transaction(callbackTransactionBody, throwException);
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Database/Extensions/DbSessionExtension.cs b/AyCode.Database/Extensions/DbSessionExtension.cs
new file mode 100644
index 0000000..cc23631
--- /dev/null
+++ b/AyCode.Database/Extensions/DbSessionExtension.cs
@@ -0,0 +1,41 @@
+using AyCode.Core.Consts;
+using AyCode.Core.Helpers;
+using AyCode.Core.Logger;
+using AyCode.Database.DbContexts;
+using AyCode.Interfaces.Entities;
+
+namespace AyCode.Database.Extensions;
+
+public static class DbSessionExtension
+{
+ public static Task SessionAsync(this TDbContext ctx, Func callback) where TDbContext : DbContextBase
+ => TaskHelper.ToThreadPoolTask(() => ctx.Session(callback));
+
+ public static TResultType Session(this TDbContext ctx, Func callback) where TDbContext : DbContextBase
+ {
+ TResultType result;
+
+ try
+ {
+ result = callback(ctx);
+ }
+ catch (Exception ex)
+ {
+ var errorText = $"Session({ctx}) callback error...{AcEnv.NL}";
+ Logger.Error($"{errorText}", ex);
+
+ throw new Exception($"{errorText}{ex}");
+ }
+
+ return result;
+ }
+
+
+ public static IEnumerable Session(this TDbContext ctx, Func> callback)
+ where TEntity : IEntity
+ where TDbContext : DbContextBase
+ {
+ foreach (var entity in callback(ctx))
+ yield return entity;
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Database/Extensions/DbTransactionExtension.cs b/AyCode.Database/Extensions/DbTransactionExtension.cs
new file mode 100644
index 0000000..5c65185
--- /dev/null
+++ b/AyCode.Database/Extensions/DbTransactionExtension.cs
@@ -0,0 +1,86 @@
+using AyCode.Core.Helpers;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using AyCode.Database.DbContexts;
+using AyCode.Core.Logger;
+using AyCode.Core.Consts;
+
+namespace AyCode.Database.Extensions;
+
+public static class DbTransactionExtension
+{
+ ///
+ /// BeginTransaction
+ ///
+ ///
+ private static IDbContextTransaction OpenTransaction(this TDbContext ctx) where TDbContext : DbContextBase
+ {
+ ctx.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never;
+
+ return ctx.Database.BeginTransaction(); //IDbContextTransaction
+ }
+
+ ///
+ ///
+ ///
+ /// True, ha sikeres volt a Commit.
+ private static bool CommitTransaction(this TDbContext ctx, bool throwException = false) where TDbContext : DbContextBase
+ {
+ var result = false;
+ var transaction = ctx.Database?.CurrentTransaction;
+
+ if (transaction == null) return false;
+
+ try
+ {
+ ctx.SaveChanges();
+ transaction.Commit();
+
+ result = true;
+ }
+ catch (Exception ex)
+ {
+ var errorText = $"Transaction({ctx}) transaction ROLLBACK!{AcEnv.NL}";
+
+ transaction.Rollback();
+
+ if (throwException) throw new Exception(errorText, ex);
+ Logger.Error($"{errorText}", ex);
+ }
+ return result;
+ }
+
+ public static Task TransactionAsync(this TDbContext ctx, Func callbackTransactionBody, bool throwException = false) where TDbContext : DbContextBase
+ => TaskHelper.ToThreadPoolTask(() => ctx.Transaction(callbackTransactionBody, throwException));
+
+ public static bool Transaction(this TDbContext ctx, Func callbackTransactionBody, bool throwException = false) where TDbContext : DbContextBase
+ {
+ var result = false;
+
+ try
+ {
+ using var transaction = ctx.OpenTransaction();
+
+ if (callbackTransactionBody(ctx)) result = ctx.CommitTransaction(throwException);
+ else
+ {
+ transaction.Rollback();
+ Logger.Warning($"Transaction({ctx}) transaction ROLLBACK!");
+ }
+ }
+ catch (Exception ex)
+ {
+ if (throwException) throw;
+
+ result = false;
+ Logger.Error($"Transaction({ctx}) transaction error...{AcEnv.NL}", ex);
+ }
+ finally
+ {
+ //result = ctx.CloseDbContext(false) && result;
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Entities/Users/UserBase.cs b/AyCode.Entities/Users/UserBase.cs
index 19ac57e..1b23c05 100644
--- a/AyCode.Entities/Users/UserBase.cs
+++ b/AyCode.Entities/Users/UserBase.cs
@@ -12,43 +12,41 @@ namespace AyCode.Entities.Users
[Table("Users")]
public class UserBase : IUserBase
{
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public Guid Id { get; set; }
+ [Column("Email")]
+ public string EmailAddress { get; set; }
+ //public string NormalizedEmail { get; set; }
+ public string PhoneNumber { get; set; }
+ public string Password { get; set; }
+ public string? RefreshToken { get; set; }
+
+ public bool EmailConfirmed { get; set; }
+
+ public DateTime Created { get; set; }
+ public DateTime Modified { get; set; }
+
+
public UserBase() { }
public UserBase(string email, string password) : this(Guid.NewGuid(), email, password) { }
public UserBase(Guid id, string email, string password) : this()
{
Id = id;
- Email = email;
+ EmailAddress = email;
Password = password;
}
public UserBase(string email, string phoneNumber, string password) : this(Guid.NewGuid(), email, phoneNumber, password) { }
- public UserBase(Guid id, string email, string phoneNumber, string password) : this()
+ public UserBase(Guid id, string email, string phoneNumber, string password) : this(id, email, password)
{
- Id = id;
- Email = email;
PhoneNumber = phoneNumber;
- Password = password;
}
public UserBase(string email, string phoneNumber, string password, string refreshToken) : this(Guid.NewGuid(), email, phoneNumber, password, refreshToken) { }
- public UserBase(Guid id, string email, string phoneNumber, string password, string refreshToken) : this()
+ public UserBase(Guid id, string email, string phoneNumber, string password, string refreshToken) : this(id, email, phoneNumber, password)
{
- Id = id;
- Email = email;
- PhoneNumber = phoneNumber;
- Password = password;
RefreshToken = refreshToken;
}
-
- [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
- public Guid Id { get; set; }
- public string Email { get; set; }
- public string PhoneNumber { get; set; }
- public string Password { get; set; }
- public string? RefreshToken { get; set; }
-
- public DateTime Created { get; set; }
- public DateTime Modified { get; set; }
}
}
diff --git a/AyCode.Entities/Users/UserTokenBase.cs b/AyCode.Entities/Users/UserTokenBase.cs
new file mode 100644
index 0000000..e34812f
--- /dev/null
+++ b/AyCode.Entities/Users/UserTokenBase.cs
@@ -0,0 +1,23 @@
+using AyCode.Interfaces.Users;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations;
+
+namespace AyCode.Entities.Users;
+
+[Table("UserToken")]
+public class UserTokenBase : IUserTokenBase
+{
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; set; }
+
+ [Required] public Guid UserId { get; set; }
+ [Required] public bool IsActive { get; set; }
+
+ [Required, MaxLength(32)]
+ public string Token { get; set; }
+ public DateTime? TokenSent { get; set; }
+ public DateTime? TokenExpiration { get; set; }
+
+ public DateTime Created { get; set; }
+ public DateTime Modified { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Interfaces/Users/IEmailAddress.cs b/AyCode.Interfaces/Users/IEmailAddress.cs
new file mode 100644
index 0000000..5368146
--- /dev/null
+++ b/AyCode.Interfaces/Users/IEmailAddress.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AyCode.Interfaces.Users;
+
+public interface IEmailAddress
+{
+ [MaxLength(150)]
+ string EmailAddress { get; set; }
+
+ bool EmailConfirmed { get; set; }
+ //[MaxLength(150)]
+ //string NormalizedEmail { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Interfaces/Users/IPassword.cs b/AyCode.Interfaces/Users/IPassword.cs
new file mode 100644
index 0000000..b08d3ce
--- /dev/null
+++ b/AyCode.Interfaces/Users/IPassword.cs
@@ -0,0 +1,6 @@
+namespace AyCode.Interfaces.Users;
+
+public interface IPassword
+{
+ string Password { get; set; }
+}
\ No newline at end of file
diff --git a/AyCode.Interfaces/Users/IUserBase.cs b/AyCode.Interfaces/Users/IUserBase.cs
index b80fb9f..153af31 100644
--- a/AyCode.Interfaces/Users/IUserBase.cs
+++ b/AyCode.Interfaces/Users/IUserBase.cs
@@ -4,8 +4,6 @@ using AyCode.Interfaces.TimeStampInfo;
namespace AyCode.Interfaces.Users;
-public interface IUserBase : IEntityGuid, ITimeStampInfo
+public interface IUserBase : IEntityGuid, IEmailAddress, IPassword, ITimeStampInfo
{
- string Email { get; }
- string Password { get; }
}
\ No newline at end of file
diff --git a/AyCode.Interfaces/Users/IUserTokenBase.cs b/AyCode.Interfaces/Users/IUserTokenBase.cs
new file mode 100644
index 0000000..a629c67
--- /dev/null
+++ b/AyCode.Interfaces/Users/IUserTokenBase.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using AyCode.Interfaces.Entities;
+using AyCode.Interfaces.TimeStampInfo;
+
+namespace AyCode.Interfaces.Users;
+
+public interface IUserTokenBase : IEntity, ITimeStampInfo
+{
+ public Guid UserId { get; set; }
+ public bool IsActive { get; set; }
+
+ public string Token { get; set; }
+ public DateTime? TokenSent { get; set; }
+ public DateTime? TokenExpiration { get; set; }
+}
\ No newline at end of file