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