From 7374d47769f65b3e8e4d28e8052a6df83d6d25ce Mon Sep 17 00:00:00 2001 From: "Amir.H Khademi" Date: Sat, 9 Mar 2024 19:53:01 +0330 Subject: [PATCH] add project layers --- .config/dotnet-tools.json | 12 + HamyanEdalat.Common/CommonConfig.cs | 7 + .../Extensions/AssertExtensions.cs | 30 ++ .../Extensions/BoolExtensions.cs | 9 + .../Extensions/ClassDisplayExtensions.cs | 39 ++ .../Extensions/DateTimeExtensions.cs | 59 +++ .../Extensions/EnumExtensions.cs | 54 +++ .../Extensions/NewtonJsonExtensions.cs | 15 + .../Extensions/PhoneNumberExtensions.cs | 41 ++ .../Extensions/PropertyExtensions.cs | 16 + .../Extensions/RandomExtensions.cs | 12 + .../Extensions/StringExtensions.cs | 165 ++++++++ .../Extensions/ValidationExtensions.cs | 21 + .../HamyanEdalat.Common.csproj | 39 ++ HamyanEdalat.Common/Models/Api/AccessToken.cs | 76 ++++ .../Models/Api/ApiResultStatusCode.cs | 41 ++ HamyanEdalat.Common/Models/Api/AppSettings.cs | 7 + .../Models/Api/FileUploadRequest.cs | 19 + .../Models/Api/FileUploadResponse.cs | 9 + HamyanEdalat.Common/Models/Api/HealthCheck.cs | 12 + .../Models/Api/ResponseFile.cs | 9 + .../Models/Api/TokenRequest.cs | 18 + .../Models/Entity/ApiEntity.cs | 48 +++ .../Models/Entity/IApiEntity.cs | 13 + .../Models/Entity/PageClassDisplay.cs | 25 ++ .../Models/Exception/AppException.cs | 26 ++ .../Models/Exception/BaseApiException.cs | 91 +++++ .../Models/Exception/ValidationException.cs | 20 + .../Models/IScopedDependency.cs | 6 + HamyanEdalat.Common/Models/Mapper/BaseDto.cs | 98 +++++ HamyanEdalat.Common/Models/Mapper/IBaseDto.cs | 11 + .../Models/Report/ChartUnit.cs | 38 ++ .../Models/Report/ReportRequest.cs | 24 ++ .../Models/Report/ReportResult.cs | 34 ++ .../Models/Report/ReportableItem.cs | 89 +++++ .../Abstracts/IPaymentService.cs | 7 + HamyanEdalat.Core/Abstracts/ISmsService.cs | 7 + .../Abstracts/IStorageService.cs | 9 + .../Abstracts/IUploadFileService.cs | 6 + .../BaseServices/Abstracts/IJwtService.cs | 9 + HamyanEdalat.Core/BaseServices/JwtService.cs | 132 +++++++ HamyanEdalat.Core/CoreConfig.cs | 7 + .../CoreServices/Abstracts/IAccountService.cs | 11 + .../CoreServices/AccountService.cs | 156 ++++++++ .../EntityServices/Abstracts/IUserService.cs | 22 ++ .../EntityServices/UserService.cs | 366 ++++++++++++++++++ HamyanEdalat.Core/HamyanEdalat.Core.csproj | 56 +++ HamyanEdalat.Core/Models/Api/ApiResult.cs | 128 ++++++ HamyanEdalat.Core/Utilities/ImageConvertor.cs | 19 + .../Commands/BlogCategoryCommands.cs | 5 + .../CommandQueries/Commands/BlogCommands.cs | 5 + .../Queries/BlogCategoryQueries.cs | 4 + .../CommandQueries/Queries/BlogQueries.cs | 6 + HamyanEdalat.Domain/DomainConfig.cs | 7 + .../Dtos/LargDto/BlogCategoryLDto.cs | 9 + HamyanEdalat.Domain/Dtos/LargDto/BlogLDto.cs | 14 + .../Dtos/RequestDtos/LoginRequestDto.cs | 8 + .../Dtos/RequestDtos/RoleActionRequestDto.cs | 10 + .../Dtos/RequestDtos/SignUpRequestDto.cs | 7 + .../Dtos/RequestDtos/UserActionRequestDto.cs | 14 + .../Dtos/ResponseDtos/GetBlogsResponseDto.cs | 7 + .../Dtos/ResponseDtos/PagerResponseDto.cs | 8 + .../Dtos/ResponseDtos/ProfileResponseDto.cs | 8 + .../ResponseDtos/VerifyCodeResponseDto.cs | 7 + .../Dtos/SmallDto/ApplicationUserSDto.cs | 21 + .../Dtos/SmallDto/BlogCategorySDto.cs | 7 + HamyanEdalat.Domain/Dtos/SmallDto/BlogSDto.cs | 13 + .../Dtos/SmallDto/StorageFileSDto.cs | 24 ++ .../Entities/Blogs/Blog.Aggregate.cs | 32 ++ HamyanEdalat.Domain/Entities/Blogs/Blog.cs | 34 ++ .../Entities/Blogs/BlogCategory.cs | 21 + .../Entities/Blogs/BlogStorageFile.cs | 16 + .../StorageFiles/StorageFile.Aggregate.cs | 9 + .../Entities/StorageFiles/StorageFile.cs | 27 ++ .../Entities/Users/ApplicationRole.cs | 8 + .../Entities/Users/ApplicationUser.cs | 19 + HamyanEdalat.Domain/Entities/Users/Gender.cs | 9 + .../Entities/Users/SignUpStatus.cs | 8 + HamyanEdalat.Domain/Enums/StorageFileType.cs | 9 + HamyanEdalat.Domain/FodyWeavers.xml | 3 + .../HamyanEdalat.Domain.csproj | 72 ++++ .../Mappers/ApplicationUserMapper.g.cs | 93 +++++ .../Mappers/BlogCategoryMapper.g.cs | 294 ++++++++++++++ HamyanEdalat.Domain/Mappers/BlogMapper.g.cs | 340 ++++++++++++++++ .../Mappers/BlogStorageFileMapper.g.cs | 6 + .../Mappers/StorageFileMapper.g.cs | 85 ++++ .../Models/Claims/ApplicationClaims.cs | 66 ++++ .../Models/Claims/ApplicationPermission.cs | 10 + HamyanEdalat.Domain/Models/Claims/ClaimDto.cs | 16 + .../Models/Claims/CustomClaimType.cs | 7 + .../Models/Settings/SiteSettings.cs | 31 ++ .../HamyanEdalat.Infrastructure.csproj | 32 ++ .../InfrastructureConfig.cs | 7 + .../Abstracts/ICurrentUserService.cs | 11 + .../Behaviors/ValidationBehavior.cs | 33 ++ .../DbContextOptionCustomExtensionsInfo.cs | 59 +++ .../Extensions/ModelBuilderExtensions.cs | 190 +++++++++ .../HamyanEdalat.Repository.csproj | 73 ++++ .../CreateBlogCategoryCommandHandler.cs | 19 + .../DeleteBlogCategoryCommandHandler.cs | 24 ++ .../GetBlogCategoriesQueryHandler.cs | 21 + .../GetBlogCategoryCommandHandler.cs | 23 ++ .../UpdateBlogCategoryCommandHandler.cs | 29 ++ .../CreateBlogCategoryCommandValidator.cs | 12 + .../Blogs/CreateBlogCommandHandler.cs | 26 ++ .../Blogs/DeleteBlogCommandHandler.cs | 26 ++ .../Handlers/Blogs/GetBlogQueryHandler.cs | 24 ++ .../Handlers/Blogs/GetBlogsQueryHandler.cs | 29 ++ .../Blogs/UpdateBlogCommandHandler.cs | 32 ++ .../Validators/CreateBlogCommandValidator.cs | 27 ++ .../Models/ApplicationContext.cs | 49 +++ .../Repositories/Base/BaseRepository.cs | 115 ++++++ .../Base/Contracts/IBaseRepository.cs | 6 + .../Base/Contracts/IReadRepository.cs | 13 + .../Base/Contracts/IRepository.cs | 10 + .../Base/Contracts/IRepositoryWrapper.cs | 11 + .../Base/Contracts/IWriteRepository.cs | 15 + .../Repositories/Base/ReadRepository.cs | 44 +++ .../Repositories/Base/Repository.cs | 36 ++ .../Repositories/Base/RepositoryWrapper.cs | 64 +++ .../Repositories/Base/WriteRepository.cs | 94 +++++ .../Repositories/UnitOfWork/IUnitOfWork.cs | 9 + .../Repositories/UnitOfWork/UnitOfWork.cs | 61 +++ HamyanEdalat.Repository/RepositoryConfig.cs | 7 + .../Abstracts/IDbInitializerService.cs | 9 + .../Services/DbInitializerService.cs | 145 +++++++ HamyanEdalat.sln | 30 ++ 127 files changed, 4982 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 HamyanEdalat.Common/CommonConfig.cs create mode 100644 HamyanEdalat.Common/Extensions/AssertExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/BoolExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/ClassDisplayExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/DateTimeExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/EnumExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/NewtonJsonExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/PhoneNumberExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/PropertyExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/RandomExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/StringExtensions.cs create mode 100644 HamyanEdalat.Common/Extensions/ValidationExtensions.cs create mode 100644 HamyanEdalat.Common/HamyanEdalat.Common.csproj create mode 100644 HamyanEdalat.Common/Models/Api/AccessToken.cs create mode 100644 HamyanEdalat.Common/Models/Api/ApiResultStatusCode.cs create mode 100644 HamyanEdalat.Common/Models/Api/AppSettings.cs create mode 100644 HamyanEdalat.Common/Models/Api/FileUploadRequest.cs create mode 100644 HamyanEdalat.Common/Models/Api/FileUploadResponse.cs create mode 100644 HamyanEdalat.Common/Models/Api/HealthCheck.cs create mode 100644 HamyanEdalat.Common/Models/Api/ResponseFile.cs create mode 100644 HamyanEdalat.Common/Models/Api/TokenRequest.cs create mode 100644 HamyanEdalat.Common/Models/Entity/ApiEntity.cs create mode 100644 HamyanEdalat.Common/Models/Entity/IApiEntity.cs create mode 100644 HamyanEdalat.Common/Models/Entity/PageClassDisplay.cs create mode 100644 HamyanEdalat.Common/Models/Exception/AppException.cs create mode 100644 HamyanEdalat.Common/Models/Exception/BaseApiException.cs create mode 100644 HamyanEdalat.Common/Models/Exception/ValidationException.cs create mode 100644 HamyanEdalat.Common/Models/IScopedDependency.cs create mode 100644 HamyanEdalat.Common/Models/Mapper/BaseDto.cs create mode 100644 HamyanEdalat.Common/Models/Mapper/IBaseDto.cs create mode 100644 HamyanEdalat.Common/Models/Report/ChartUnit.cs create mode 100644 HamyanEdalat.Common/Models/Report/ReportRequest.cs create mode 100644 HamyanEdalat.Common/Models/Report/ReportResult.cs create mode 100644 HamyanEdalat.Common/Models/Report/ReportableItem.cs create mode 100644 HamyanEdalat.Core/Abstracts/IPaymentService.cs create mode 100644 HamyanEdalat.Core/Abstracts/ISmsService.cs create mode 100644 HamyanEdalat.Core/Abstracts/IStorageService.cs create mode 100644 HamyanEdalat.Core/Abstracts/IUploadFileService.cs create mode 100644 HamyanEdalat.Core/BaseServices/Abstracts/IJwtService.cs create mode 100644 HamyanEdalat.Core/BaseServices/JwtService.cs create mode 100644 HamyanEdalat.Core/CoreConfig.cs create mode 100644 HamyanEdalat.Core/CoreServices/Abstracts/IAccountService.cs create mode 100644 HamyanEdalat.Core/CoreServices/AccountService.cs create mode 100644 HamyanEdalat.Core/EntityServices/Abstracts/IUserService.cs create mode 100644 HamyanEdalat.Core/EntityServices/UserService.cs create mode 100644 HamyanEdalat.Core/HamyanEdalat.Core.csproj create mode 100644 HamyanEdalat.Core/Models/Api/ApiResult.cs create mode 100644 HamyanEdalat.Core/Utilities/ImageConvertor.cs create mode 100644 HamyanEdalat.Domain/CommandQueries/Commands/BlogCategoryCommands.cs create mode 100644 HamyanEdalat.Domain/CommandQueries/Commands/BlogCommands.cs create mode 100644 HamyanEdalat.Domain/CommandQueries/Queries/BlogCategoryQueries.cs create mode 100644 HamyanEdalat.Domain/CommandQueries/Queries/BlogQueries.cs create mode 100644 HamyanEdalat.Domain/DomainConfig.cs create mode 100644 HamyanEdalat.Domain/Dtos/LargDto/BlogCategoryLDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/LargDto/BlogLDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/RequestDtos/LoginRequestDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/RequestDtos/SignUpRequestDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/RequestDtos/UserActionRequestDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/ResponseDtos/GetBlogsResponseDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/ResponseDtos/PagerResponseDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/SmallDto/ApplicationUserSDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/SmallDto/BlogCategorySDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/SmallDto/BlogSDto.cs create mode 100644 HamyanEdalat.Domain/Dtos/SmallDto/StorageFileSDto.cs create mode 100644 HamyanEdalat.Domain/Entities/Blogs/Blog.Aggregate.cs create mode 100644 HamyanEdalat.Domain/Entities/Blogs/Blog.cs create mode 100644 HamyanEdalat.Domain/Entities/Blogs/BlogCategory.cs create mode 100644 HamyanEdalat.Domain/Entities/Blogs/BlogStorageFile.cs create mode 100644 HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.Aggregate.cs create mode 100644 HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.cs create mode 100644 HamyanEdalat.Domain/Entities/Users/ApplicationRole.cs create mode 100644 HamyanEdalat.Domain/Entities/Users/ApplicationUser.cs create mode 100644 HamyanEdalat.Domain/Entities/Users/Gender.cs create mode 100644 HamyanEdalat.Domain/Entities/Users/SignUpStatus.cs create mode 100644 HamyanEdalat.Domain/Enums/StorageFileType.cs create mode 100644 HamyanEdalat.Domain/FodyWeavers.xml create mode 100644 HamyanEdalat.Domain/HamyanEdalat.Domain.csproj create mode 100644 HamyanEdalat.Domain/Mappers/ApplicationUserMapper.g.cs create mode 100644 HamyanEdalat.Domain/Mappers/BlogCategoryMapper.g.cs create mode 100644 HamyanEdalat.Domain/Mappers/BlogMapper.g.cs create mode 100644 HamyanEdalat.Domain/Mappers/BlogStorageFileMapper.g.cs create mode 100644 HamyanEdalat.Domain/Mappers/StorageFileMapper.g.cs create mode 100644 HamyanEdalat.Domain/Models/Claims/ApplicationClaims.cs create mode 100644 HamyanEdalat.Domain/Models/Claims/ApplicationPermission.cs create mode 100644 HamyanEdalat.Domain/Models/Claims/ClaimDto.cs create mode 100644 HamyanEdalat.Domain/Models/Claims/CustomClaimType.cs create mode 100644 HamyanEdalat.Domain/Models/Settings/SiteSettings.cs create mode 100644 HamyanEdalat.Infrastructure/HamyanEdalat.Infrastructure.csproj create mode 100644 HamyanEdalat.Infrastructure/InfrastructureConfig.cs create mode 100644 HamyanEdalat.Repository/Abstracts/ICurrentUserService.cs create mode 100644 HamyanEdalat.Repository/Behaviors/ValidationBehavior.cs create mode 100644 HamyanEdalat.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs create mode 100644 HamyanEdalat.Repository/Extensions/ModelBuilderExtensions.cs create mode 100644 HamyanEdalat.Repository/HamyanEdalat.Repository.csproj create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/CreateBlogCategoryCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/DeleteBlogCategoryCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoriesQueryHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoryCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/UpdateBlogCategoryCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/BlogCategories/Validators/CreateBlogCategoryCommandValidator.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/CreateBlogCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/DeleteBlogCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/GetBlogQueryHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/GetBlogsQueryHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/UpdateBlogCommandHandler.cs create mode 100644 HamyanEdalat.Repository/Handlers/Blogs/Validators/CreateBlogCommandValidator.cs create mode 100644 HamyanEdalat.Repository/Models/ApplicationContext.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/BaseRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Contracts/IBaseRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Contracts/IReadRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Contracts/IRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Contracts/IWriteRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/ReadRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/Repository.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/RepositoryWrapper.cs create mode 100644 HamyanEdalat.Repository/Repositories/Base/WriteRepository.cs create mode 100644 HamyanEdalat.Repository/Repositories/UnitOfWork/IUnitOfWork.cs create mode 100644 HamyanEdalat.Repository/Repositories/UnitOfWork/UnitOfWork.cs create mode 100644 HamyanEdalat.Repository/RepositoryConfig.cs create mode 100644 HamyanEdalat.Repository/Services/Abstracts/IDbInitializerService.cs create mode 100644 HamyanEdalat.Repository/Services/DbInitializerService.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..db4df7b --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "mapster.tool": { + "version": "8.4.0", + "commands": [ + "dotnet-mapster" + ] + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/CommonConfig.cs b/HamyanEdalat.Common/CommonConfig.cs new file mode 100644 index 0000000..d621010 --- /dev/null +++ b/HamyanEdalat.Common/CommonConfig.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Common +{ + public class CommonConfig + { + + } +} diff --git a/HamyanEdalat.Common/Extensions/AssertExtensions.cs b/HamyanEdalat.Common/Extensions/AssertExtensions.cs new file mode 100644 index 0000000..14ad5f9 --- /dev/null +++ b/HamyanEdalat.Common/Extensions/AssertExtensions.cs @@ -0,0 +1,30 @@ +using System.Collections; + +namespace HamyanEdalat.Common.Extensions +{ + public static class AssertExtensions + { + public static void NotNull(T obj, string name, string message = null) + where T : class + { + if (obj is null) + throw new ArgumentNullException($"{name} : {typeof(T)}", message); + } + + public static void NotNull(T? obj, string name, string message = null) + where T : struct + { + if (!obj.HasValue) + throw new ArgumentNullException($"{name} : {typeof(T)}", message); + } + + public static void NotEmpty(T obj, string name, string message = null, T defaultValue = null) + where T : class + { + if (obj == defaultValue + || obj is string str && string.IsNullOrWhiteSpace(str) + || obj is IEnumerable list && !list.Cast().Any()) + throw new ArgumentException("Argument is empty : " + message, $"{name} : {typeof(T)}"); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/BoolExtensions.cs b/HamyanEdalat.Common/Extensions/BoolExtensions.cs new file mode 100644 index 0000000..052717e --- /dev/null +++ b/HamyanEdalat.Common/Extensions/BoolExtensions.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Common.Extensions; + +public static class BoolExtensions +{ + public static string ToPersianString(this bool value) + { + return value ? "بله" : "خیر"; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/ClassDisplayExtensions.cs b/HamyanEdalat.Common/Extensions/ClassDisplayExtensions.cs new file mode 100644 index 0000000..900bd8e --- /dev/null +++ b/HamyanEdalat.Common/Extensions/ClassDisplayExtensions.cs @@ -0,0 +1,39 @@ +using HamyanEdalat.Common.Models.Entity; + +namespace HamyanEdalat.Common.Extensions +{ + public static class ClassDisplayExtensions + { + public static string GetDisplayAttributeName() + { + var attrs = + Attribute.GetCustomAttributes(typeof(T)); + + foreach (var attr in attrs) + { + var displayAttribute = attr as PageClassDisplay; + if (displayAttribute == null) + continue; + return displayAttribute.GetName(); + } + + return null; + } + + public static string GetDisplayAttributeDescription() + { + var attrs = + Attribute.GetCustomAttributes(typeof(T)); + + foreach (var attr in attrs) + { + var displayAttribute = attr as PageClassDisplay; + if (displayAttribute == null) + continue; + return displayAttribute.GetDescription(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/DateTimeExtensions.cs b/HamyanEdalat.Common/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..e306418 --- /dev/null +++ b/HamyanEdalat.Common/Extensions/DateTimeExtensions.cs @@ -0,0 +1,59 @@ +namespace HamyanEdalat.Common.Extensions +{ + public static class DateTimeExtensions + { + public static string GetPersianDayOfWeek(this DayOfWeek dayOfWeek) + { + switch (dayOfWeek) + { + case DayOfWeek.Friday: + return "جمعه"; + case DayOfWeek.Monday: + return "دوشنبه"; + case DayOfWeek.Saturday: + return "شنبه"; + case DayOfWeek.Sunday: + return "یکشنبه"; + case DayOfWeek.Thursday: + return "پنج شنبه"; + case DayOfWeek.Tuesday: + return "سه شنبه"; + case DayOfWeek.Wednesday: + return "چهارشنبه"; + } + + return ""; + } + + public static DateTime UnixTimeStampToDateTime(double unixTimeStamp) + { + // Unix timestamp is seconds past epoch + var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddMilliseconds(unixTimeStamp).ToLocalTime(); + return dtDateTime; + } + + public static long DateTimeToUnixTimeStamp(DateTime dateTime) + { + return ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds(); + } + + public static int DifferenceByDay(DateTime originDateTime, DateTime destDateTime) + { + return (int)(destDateTime - originDateTime).TotalDays; + } + + public static int DifferenceByHoure(DateTime originDateTime, DateTime destDateTime) + { + return (int)(destDateTime - originDateTime).TotalHours; + } + + public static TimeSpan Difference(DateTime originDateTime, DateTime destDateTime) + { + var durateion = (destDateTime - originDateTime).Duration(); + return durateion; + } + public static PersianDateTime ToPersianDateTime(this DateTime dateTime) + => new PersianDateTime(dateTime); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/EnumExtensions.cs b/HamyanEdalat.Common/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..95588e5 --- /dev/null +++ b/HamyanEdalat.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,54 @@ +using System.Reflection; + +namespace HamyanEdalat.Common.Extensions +{ + public enum DisplayProperty + { + Description, + GroupName, + Name, + Prompt, + ShortName, + Order + } + + public static class EnumExtensions + { + public static IEnumerable GetEnumValues(this T input) where T : struct + { + if (!typeof(T).IsEnum) + throw new NotSupportedException(); + + return Enum.GetValues(input.GetType()).Cast(); + } + + public static IEnumerable GetEnumFlags(this T input) where T : struct + { + if (!typeof(T).IsEnum) + throw new NotSupportedException(); + + foreach (var value in Enum.GetValues(input.GetType())) + if ((input as Enum).HasFlag(value as Enum)) + yield return (T)value; + } + + public static string ToDisplay(this Enum value, DisplayProperty property = DisplayProperty.Name) + { + AssertExtensions.NotNull(value, nameof(value)); + + var attribute = value.GetType().GetField(value.ToString()) + .GetCustomAttributes(false).FirstOrDefault(); + + if (attribute == null) + return value.ToString(); + + var propValue = attribute.GetType().GetProperty(property.ToString()).GetValue(attribute, null); + return propValue.ToString(); + } + + public static Dictionary ToDictionary(this Enum value) + { + return Enum.GetValues(value.GetType()).Cast().ToDictionary(p => Convert.ToInt32(p), q => ToDisplay(q)); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/NewtonJsonExtensions.cs b/HamyanEdalat.Common/Extensions/NewtonJsonExtensions.cs new file mode 100644 index 0000000..4ec7f3f --- /dev/null +++ b/HamyanEdalat.Common/Extensions/NewtonJsonExtensions.cs @@ -0,0 +1,15 @@ +namespace HamyanEdalat.Common.Extensions +{ + public static class NewtonJsonExtensions + { + public static JsonSerializerSettings CamelCaseSerialize => + new() + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }, + Formatting = Formatting.Indented + }; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/PhoneNumberExtensions.cs b/HamyanEdalat.Common/Extensions/PhoneNumberExtensions.cs new file mode 100644 index 0000000..553ea9e --- /dev/null +++ b/HamyanEdalat.Common/Extensions/PhoneNumberExtensions.cs @@ -0,0 +1,41 @@ +using System.Text.RegularExpressions; + +namespace HamyanEdalat.Common.Extensions; +public static class PhoneNumberExtensions +{ + public static bool CheckPhoneNumber(string phoneNumber) + { + var regex = new Regex(@"(^(989|0989|\+989|09|9)[0-9]{9}$)"); + return regex.IsMatch(phoneNumber); + } + public static string GetVerifyFromPhoneNumber(string phoneNumber) + { + var dateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute / 5, 0); + var timeStamp = ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds() / 10000; + + int FTD = int.Parse(string.Format("{0}{1}{2}", + GetSumDigit(int.Parse(string.Format("{0}{1}{2}", phoneNumber[5], phoneNumber[7], phoneNumber[9]))) % 10, + GetSumDigit(int.Parse(string.Format("{0}{1}{2}", phoneNumber[4], phoneNumber[6], phoneNumber[8]))) % 10, + GetSumDigit(int.Parse(string.Format("{0}{1}{2}", phoneNumber[10], phoneNumber[9], phoneNumber[8]))) % 10)); + int ATD = GetSumDigit(((int)timeStamp % 1000) + FTD); + timeStamp = (int)timeStamp / 1000; + int BTD = GetSumDigit(((int)timeStamp % 1000) + ATD); + timeStamp = (int)timeStamp / 1000; + int CTD = GetSumDigit(((int)timeStamp % 1000) + ATD); + FTD = GetSumDigit(FTD); + if (ATD % 2 == 0) + return string.Format("{0}{1}{2}{3}", GetSumDigit(ATD) % 10, GetSumDigit(BTD) % 10, GetSumDigit(CTD) % 10, GetSumDigit(FTD) % 10); + else + return string.Format("{0}{1}{2}{3}", ATD % 10, BTD % 10, CTD % 10, FTD % 10); + } + private static int GetSumDigit(int number) + { + string sNumber = number.ToString(); + int total = 0; + foreach (var s in sNumber) + total += int.Parse(s.ToString()); + return total; + } + + +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/PropertyExtensions.cs b/HamyanEdalat.Common/Extensions/PropertyExtensions.cs new file mode 100644 index 0000000..8e7a4f4 --- /dev/null +++ b/HamyanEdalat.Common/Extensions/PropertyExtensions.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace HamyanEdalat.Common.Extensions +{ + public static class PropertyExtensions + { + public static string GetPropertyDisplayName(this MemberInfo propertyExpression) + { + var memberInfo = propertyExpression; + var attr = memberInfo.GetCustomAttributes().FirstOrDefault(); + if (attr == null) return memberInfo.Name; + + return attr.Name; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/RandomExtensions.cs b/HamyanEdalat.Common/Extensions/RandomExtensions.cs new file mode 100644 index 0000000..b11a9ab --- /dev/null +++ b/HamyanEdalat.Common/Extensions/RandomExtensions.cs @@ -0,0 +1,12 @@ +namespace HamyanEdalat.Common.Extensions +{ + public static class RandomExtensions + { + public static T RandomItem(List originList) + { + var random = new Random(DateTime.Now.Millisecond); + var rand = random.Next(0, originList.Count - 1); + return originList[rand]; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/StringExtensions.cs b/HamyanEdalat.Common/Extensions/StringExtensions.cs new file mode 100644 index 0000000..40a81b4 --- /dev/null +++ b/HamyanEdalat.Common/Extensions/StringExtensions.cs @@ -0,0 +1,165 @@ +namespace HamyanEdalat.Common.Extensions +{ + public static class StringExtensions + { + public static string ToPriceWhitPriceType(this long price, string priceType) + { + return price.ToString("N0") + " " + priceType; + } + + public static string ToPriceWhitPriceType(this decimal price, string priceType) + { + return price.ToString("N0") + " " + priceType; + } + + public static string ToPriceWhitPriceType(this double price, string priceType) + { + return price.ToString("N0") + " " + priceType; + } + + public static bool HasValue(this string value, bool ignoreWhiteSpace = true) + { + return ignoreWhiteSpace ? !string.IsNullOrWhiteSpace(value) : !string.IsNullOrEmpty(value); + } + + public static int ToInt(this string value) + { + return Convert.ToInt32(value); + } + + public static decimal ToDecimal(this string value) + { + return Convert.ToDecimal(value); + } + + public static string ToNumeric(this int value) + { + return value.ToString("N0"); //"123,456" + } + + public static string ToNumeric(this decimal value) + { + return value.ToString("N0"); + } + + public static string ToCurrency(this int value) + { + //fa-IR => current culture currency symbol => ریال + //123456 => "123,123ریال" + return value.ToString("C0"); + } + + public static string ToCurrency(this decimal value) + { + return value.ToString("C0"); + } + + public static string En2Fa(this string str) + { + return str.Replace("0", "۰") + .Replace("1", "۱") + .Replace("2", "۲") + .Replace("3", "۳") + .Replace("4", "۴") + .Replace("5", "۵") + .Replace("6", "۶") + .Replace("7", "۷") + .Replace("8", "۸") + .Replace("9", "۹"); + } + + public static string Fa2En(this string str) + { + return str.Replace("۰", "0") + .Replace("۱", "1") + .Replace("۲", "2") + .Replace("۳", "3") + .Replace("۴", "4") + .Replace("۵", "5") + .Replace("۶", "6") + .Replace("۷", "7") + .Replace("۸", "8") + .Replace("۹", "9") + //iphone numeric + .Replace("٠", "0") + .Replace("١", "1") + .Replace("٢", "2") + .Replace("٣", "3") + .Replace("٤", "4") + .Replace("٥", "5") + .Replace("٦", "6") + .Replace("٧", "7") + .Replace("٨", "8") + .Replace("٩", "9"); + } + + public static string FixPersianChars(this string str) + { + return str.Replace("ﮎ", "ک") + .Replace("ﮏ", "ک") + .Replace("ﮐ", "ک") + .Replace("ﮑ", "ک") + .Replace("ك", "ک") + .Replace("ي", "ی") + .Replace(" ", " ") + .Replace("‌", " ") + .Replace("ھ", "ه"); //.Replace("ئ", "ی"); + } + + public static string CleanString(this string str) + { + return str.Trim().FixPersianChars().Fa2En().NullIfEmpty(); + } + + public static string NullIfEmpty(this string str) + { + return str?.Length == 0 ? null : str; + } + + public static string GetId(int length = 8) + { + return Guid.NewGuid().ToString("N").Substring(0, length); + } + + public static string ConvertTo3Digit(this string str) + { + str = string.Concat(str.Split(',')); + var array = str.ToCharArray(); + Array.Reverse(array); + str = new string(array); + + var newStr = ""; + for (var i = 0; i < str.Length; i++) + { + newStr += str[i]; + if ((i + 1) % 3 == 0) + newStr += ","; + } + + var newarray = newStr.ToCharArray(); + Array.Reverse(newarray); + newStr = new string(newarray); + if (newStr.Length > 0 && newStr[0] == ',') + newStr = newStr.Substring(1); + + return newStr; + } + + public static string ConvertToOrginal(this string str) + { + return string.Concat(str.Split(',')); + } + + public static string CheckPhoneNumber(string phoneNumber) + { + if (phoneNumber.Substring(0, 3).Contains("+98")) + phoneNumber = phoneNumber.Replace("+98", "0"); + else if (phoneNumber.Substring(0, 2).Contains("98")) + phoneNumber = string.Concat("0", phoneNumber.Substring(2)); + else if (phoneNumber[0] != '0') + phoneNumber = string.Concat("0", phoneNumber); + + return phoneNumber; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Extensions/ValidationExtensions.cs b/HamyanEdalat.Common/Extensions/ValidationExtensions.cs new file mode 100644 index 0000000..8bf8b2d --- /dev/null +++ b/HamyanEdalat.Common/Extensions/ValidationExtensions.cs @@ -0,0 +1,21 @@ +namespace HamyanEdalat.Common.Extensions +{ + public static class ValidationExtensions + { + public static bool CheckDateIs(this DateTime dateTime, DateTime From, DateTime To) + { + if (dateTime.Date > To.Date && dateTime.Date < From.Date) + return true; + return false; + } + + public static bool CheckDateFromToNow(this DateTime dateTime, int fromDays = 5) + { + var From = DateTime.Now.AddDays(-fromDays); + var To = DateTime.Now; + if (dateTime.Date > To.Date && dateTime.Date < From.Date) + return true; + return false; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/HamyanEdalat.Common.csproj b/HamyanEdalat.Common/HamyanEdalat.Common.csproj new file mode 100644 index 0000000..74ba3af --- /dev/null +++ b/HamyanEdalat.Common/HamyanEdalat.Common.csproj @@ -0,0 +1,39 @@ + + + + + + net5.0 + 10 + enable + + + + + + + + + + + + + + + + + + + diff --git a/HamyanEdalat.Common/Models/Api/AccessToken.cs b/HamyanEdalat.Common/Models/Api/AccessToken.cs new file mode 100644 index 0000000..557fab3 --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/AccessToken.cs @@ -0,0 +1,76 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public class AccessToken + { + public AccessToken() + { + } + + public AccessToken(JwtSecurityToken securityToken) + { + access_token = new JwtSecurityTokenHandler().WriteToken(securityToken); + token_type = "Bearer"; + expire_in_datetime = securityToken.ValidTo; + expires_in = (int)(securityToken.ValidTo - DateTime.UtcNow).TotalSeconds; + } + + public string access_token { get; set; } = string.Empty; + public string refresh_token { get; set; } = string.Empty; + public string token_type { get; set; } = string.Empty; + public int expires_in { get; set; } + public string token_id { get; set; } = string.Empty; + public DateTime expire_in_datetime { get; set; } + public string BearerToken => $"Bearer {access_token}"; + } + + public class AccessToken + { + public AccessToken() + { + } + + public AccessToken(JwtSecurityToken securityToken) + { + access_token = new JwtSecurityTokenHandler().WriteToken(securityToken); + token_type = "Bearer"; + expires_in = (int)(securityToken.ValidTo - DateTime.UtcNow).TotalSeconds; + } + + public string access_token { get; set; } = string.Empty; + public string ig_access_token { get; set; } = string.Empty; + public string refresh_token { get; set; } = string.Empty; + public string token_type { get; set; } = string.Empty; + public int expires_in { get; set; } + + public TUser User { get; set; } + public string BearerToken => $"Bearer {access_token}"; + public List Permissions { get; set; } + public string RoleName { get; set; } + } + + + public class AccessToken + { + public AccessToken() + { + } + + public AccessToken(JwtSecurityToken securityToken) + { + access_token = new JwtSecurityTokenHandler().WriteToken(securityToken); + token_type = "Bearer"; + expires_in = (int)(securityToken.ValidTo - DateTime.UtcNow).TotalSeconds; + } + + public string access_token { get; set; } = string.Empty; + public string ig_access_token { get; set; } = string.Empty; + public string refresh_token { get; set; } = string.Empty; + public string token_type { get; set; } = string.Empty; + public int expires_in { get; set; } + + public TUser User { get; set; } + public string BearerToken => $"Bearer {access_token}"; + public List Permissions { get; set; } + public List Roles { get; set; } = new(); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/ApiResultStatusCode.cs b/HamyanEdalat.Common/Models/Api/ApiResultStatusCode.cs new file mode 100644 index 0000000..3e6845b --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/ApiResultStatusCode.cs @@ -0,0 +1,41 @@ +using System.Net; + +namespace HamyanEdalat.Common.Models.Api +{ + public enum ApiResultStatusCode + { + [Display(Name = "عملیات با موفقیت انجام شد")] + Success = HttpStatusCode.OK, + + [Display(Name = "خطایی در سرور رخ داده است")] + ServerError = HttpStatusCode.InternalServerError, + + [Display(Name = "پارامتر های ارسالی معتبر نیستند")] + BadRequest = HttpStatusCode.BadRequest, + + [Display(Name = "یافت نشد")] + NotFound = HttpStatusCode.NotFound, + + [Display(Name = "لیست خالی است")] + ListEmpty = 4, + + [Display(Name = "خطایی در پردازش رخ داد")] + LogicError = 5, + + [Display(Name = "موجودی کیف پول کافی نمی باشد")] + WalletBalanceNoEnough = 6, + + [Display(Name = "خطای احراز هویت")] + UnAuthorized = HttpStatusCode.Unauthorized, + + [Display(Name = "سرور خاموش شده است لطفا منتظر بمانید")] + ServerDown = HttpStatusCode.ServiceUnavailable, + + [Display(Name = "در ارسال پیامک مورد نظر مشکلی رخ داده است")] + SendSmsError = 7, + + + [Display(Name = "در ارسال درخواست به سرورهای دیگر مشکلی رخ داده است")] + RefitError = 8 + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/AppSettings.cs b/HamyanEdalat.Common/Models/Api/AppSettings.cs new file mode 100644 index 0000000..bb1aaeb --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/AppSettings.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public class AppSettings + { + public bool Seeded { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/FileUploadRequest.cs b/HamyanEdalat.Common/Models/Api/FileUploadRequest.cs new file mode 100644 index 0000000..4cb84e8 --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/FileUploadRequest.cs @@ -0,0 +1,19 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public enum FileUploadType + { + [Display(Name = "Images")] + Image, + [Display(Name = "Handouts")] + Handout, + [Display(Name = "Videos")] + Video, + } + public class FileUploadRequest + { + public string StringBaseFile { get; set; } + public string FileName { get; set; } + public string ContentType { get; set; } + public FileUploadType FileUploadType { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/FileUploadResponse.cs b/HamyanEdalat.Common/Models/Api/FileUploadResponse.cs new file mode 100644 index 0000000..0ad9727 --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/FileUploadResponse.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Common.Models.Api; + +public class FileUploadResponse +{ + public string FileUrl { get; set; } + public string FileLocation { get; set; } + public string FileName { get; set; } + public bool IsOriginal { get; set; } = true; +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/HealthCheck.cs b/HamyanEdalat.Common/Models/Api/HealthCheck.cs new file mode 100644 index 0000000..c961cdf --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/HealthCheck.cs @@ -0,0 +1,12 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public class HealthCheck + { + public bool Health { get; set; } + public string Version { get; set; } = string.Empty; + public string TotalMemory { get; set; } = string.Empty; + public string StartAt { get; set; } = string.Empty; + public string StartAtPersian { get; set; } = string.Empty; + public string MachineName { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/ResponseFile.cs b/HamyanEdalat.Common/Models/Api/ResponseFile.cs new file mode 100644 index 0000000..ce2ffd7 --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/ResponseFile.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public class ResponseFile + { + public string Url { get; set; } + public string Location { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Api/TokenRequest.cs b/HamyanEdalat.Common/Models/Api/TokenRequest.cs new file mode 100644 index 0000000..a6d99ba --- /dev/null +++ b/HamyanEdalat.Common/Models/Api/TokenRequest.cs @@ -0,0 +1,18 @@ +namespace HamyanEdalat.Common.Models.Api +{ + public class TokenRequest + { + public string grant_type { get; set; } + public string username { get; set; } + public string password { get; set; } + public string refresh_token { get; set; } + public string scope { get; set; } + + public string client_id { get; set; } + public string client_secret { get; set; } + + public string login_hash { get; set; } + public string device_id { get; set; } + public string license_id { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Entity/ApiEntity.cs b/HamyanEdalat.Common/Models/Entity/ApiEntity.cs new file mode 100644 index 0000000..f28addd --- /dev/null +++ b/HamyanEdalat.Common/Models/Entity/ApiEntity.cs @@ -0,0 +1,48 @@ +namespace HamyanEdalat.Common.Models.Entity; +public abstract class ApiEntity : IApiEntity , IEquatable +{ + [Key] + public Guid Id { get; set; } + + [Display(Name = "تاریخ حذف")] + public DateTime RemovedAt { get; set; } + + [Display(Name = "تاریخ ساخت")] + public DateTime CreatedAt { get; set; } + + [Display(Name = "ساخته شده توسط")] + public string CreatedBy { get; set; } = string.Empty; + + [Display(Name = "حذف شده")] + public bool IsRemoved { get; set; } + [Display(Name = "حذف شده توسط")] + public string RemovedBy { get; set; } = string.Empty; + [Display(Name = "اخرین تغییر در")] + public DateTime ModifiedAt { get; set; } + + [Display(Name = "اخرین تغییر توسط")] + public string ModifiedBy { get; set; } = string.Empty; + + + + public bool Equals(ApiEntity other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ApiEntity)obj); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + +} diff --git a/HamyanEdalat.Common/Models/Entity/IApiEntity.cs b/HamyanEdalat.Common/Models/Entity/IApiEntity.cs new file mode 100644 index 0000000..1b21716 --- /dev/null +++ b/HamyanEdalat.Common/Models/Entity/IApiEntity.cs @@ -0,0 +1,13 @@ +namespace HamyanEdalat.Common.Models.Entity +{ + public interface IApiEntity + { + string CreatedBy { get; } + string ModifiedBy { get; } + string RemovedBy { get; } + bool IsRemoved { get; } + DateTime CreatedAt { get; } + DateTime RemovedAt { get; } + DateTime ModifiedAt { get; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Entity/PageClassDisplay.cs b/HamyanEdalat.Common/Models/Entity/PageClassDisplay.cs new file mode 100644 index 0000000..cf7354b --- /dev/null +++ b/HamyanEdalat.Common/Models/Entity/PageClassDisplay.cs @@ -0,0 +1,25 @@ +namespace HamyanEdalat.Common.Models.Entity +{ + [AttributeUsage(AttributeTargets.Class)] + public class PageClassDisplay : Attribute + { + private readonly string _description; + private readonly string _name; + + public PageClassDisplay(string name, string description) + { + _name = name; + _description = description; + } + + public string GetName() + { + return _name; + } + + public string GetDescription() + { + return _description; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Exception/AppException.cs b/HamyanEdalat.Common/Models/Exception/AppException.cs new file mode 100644 index 0000000..b85342c --- /dev/null +++ b/HamyanEdalat.Common/Models/Exception/AppException.cs @@ -0,0 +1,26 @@ +using System.Runtime.Serialization; +using HamyanEdalat.Common.Models.Api; + +namespace HamyanEdalat.Common.Models.Exception +{ + [Serializable()] + public class AppException : System.Exception + { + protected AppException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + public ApiResultStatusCode StatusCode { get; set; } + public AppException() + { + StatusCode = ApiResultStatusCode.ServerError; + } + public AppException(string message) : base(message) + { + StatusCode = ApiResultStatusCode.ServerError; + } + + public AppException(string message, ApiResultStatusCode statusCode) : base(message) + { + StatusCode = statusCode; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Exception/BaseApiException.cs b/HamyanEdalat.Common/Models/Exception/BaseApiException.cs new file mode 100644 index 0000000..2ca2d29 --- /dev/null +++ b/HamyanEdalat.Common/Models/Exception/BaseApiException.cs @@ -0,0 +1,91 @@ +using System.Net; +using System.Runtime.Serialization; +using HamyanEdalat.Common.Models.Api; + +namespace HamyanEdalat.Common.Models.Exception +{ + [Serializable()] + public class BaseApiException : System.Exception + { + protected BaseApiException(SerializationInfo info, StreamingContext context) : base(info, context) { } + public BaseApiException() + : this(ApiResultStatusCode.ServerError) + { + } + + public BaseApiException(ApiResultStatusCode statusCode) + : this(statusCode, null) + { + } + + public BaseApiException(string message) + : this(ApiResultStatusCode.ServerError, message) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message) + : this(statusCode, message, HttpStatusCode.InternalServerError) + { + } + + public BaseApiException(string message, object additionalData) : this(ApiResultStatusCode.ServerError, message, additionalData) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, object additionalData) : this(statusCode, null, additionalData) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, object additionalData) + : this(statusCode, message, HttpStatusCode.InternalServerError, additionalData) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, HttpStatusCode httpStatusCode) + : this(statusCode, message, httpStatusCode, null) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, HttpStatusCode httpStatusCode, object additionalData) + : this(statusCode, message, httpStatusCode, null, additionalData) + { + } + + public BaseApiException(string message, System.Exception exception) + : this(ApiResultStatusCode.ServerError, message, exception) + { + } + + public BaseApiException(string message, System.Exception exception, object additionalData) + : this(ApiResultStatusCode.ServerError, message, exception, additionalData) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, System.Exception exception) + : this(statusCode, message, HttpStatusCode.InternalServerError, exception) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, System.Exception exception, object additionalData) + : this(statusCode, message, HttpStatusCode.InternalServerError, exception, additionalData) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, HttpStatusCode httpStatusCode, System.Exception exception) + : this(statusCode, message, httpStatusCode, exception, null) + { + } + + public BaseApiException(ApiResultStatusCode statusCode, string message, HttpStatusCode httpStatusCode, System.Exception exception, object additionalData) + : base(message, exception) + { + ApiStatusCode = statusCode; + HttpStatusCode = httpStatusCode; + AdditionalData = additionalData; + } + + public HttpStatusCode HttpStatusCode { get; set; } + public ApiResultStatusCode ApiStatusCode { get; set; } + public object AdditionalData { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Exception/ValidationException.cs b/HamyanEdalat.Common/Models/Exception/ValidationException.cs new file mode 100644 index 0000000..0af0b52 --- /dev/null +++ b/HamyanEdalat.Common/Models/Exception/ValidationException.cs @@ -0,0 +1,20 @@ +namespace HamyanEdalat.Common.Models.Exception; + +public class ValidationException : System.Exception +{ + public ValidationException() : base("Validation has been failed") + { + + } + + public ValidationException(params ValidationError[] validationErrors) : base($"{string.Join(",", validationErrors.Select(v => v.ErrorMessage))}") + { + + } + + public ValidationException(List validationErrors) : base($"{string.Join(",", validationErrors.Select(v => v.ErrorMessage))}") + { + + } +} +public sealed record ValidationError(string PropertyName, string ErrorMessage); \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/IScopedDependency.cs b/HamyanEdalat.Common/Models/IScopedDependency.cs new file mode 100644 index 0000000..3b7e5c5 --- /dev/null +++ b/HamyanEdalat.Common/Models/IScopedDependency.cs @@ -0,0 +1,6 @@ +namespace HamyanEdalat.Common.Models +{ + public interface IScopedDependency + { + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Mapper/BaseDto.cs b/HamyanEdalat.Common/Models/Mapper/BaseDto.cs new file mode 100644 index 0000000..5544349 --- /dev/null +++ b/HamyanEdalat.Common/Models/Mapper/BaseDto.cs @@ -0,0 +1,98 @@ +using System.ComponentModel; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using HamyanEdalat.Common.Models.Exception; + +namespace HamyanEdalat.Common.Models.Mapper +{ + /// + /// Base Dto Class initial map config between entity and dto + /// + /// Type of Dto Class + /// Type of Entity Class + public abstract class BaseDto : INotifyPropertyChanged, IBaseDto where TEntity : class where TDto : class + { + public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public static Expression> ProjectToDto + { + get => GetProjectToDto(); + } + private static Expression> GetProjectToDto() + { + var assembly = typeof(TEntity).Assembly; + var mapperName = $"{typeof(TEntity).Name}Mapper"; + var mapperType = assembly.GetTypes()?.FirstOrDefault(t => t.Name.Contains(mapperName)); + if (mapperType == null) + throw new AppException($"{typeof(TEntity).Name}Mapper Not Found!"); + if (typeof(TDto).Name.Contains("SDto")) + { + var projectProperty = mapperType.GetProperty("ProjectToSDto"); + if (projectProperty == null) + throw new AppException($"{typeof(TEntity).Name}Mapper Dont Have ProjectTo"); + return projectProperty.GetValue(null, null) as Expression>; + } + else if (typeof(TDto).Name.Contains("LDto")) + { + var projectProperty = mapperType.GetProperty("ProjectToLDto"); + if (projectProperty == null) + throw new AppException($"{typeof(TEntity).Name}Mapper Dont Have ProjectTo"); + return projectProperty.GetValue(null, null) as Expression>; + } + else + throw new AppException($"{typeof(TDto).Name} Projection Not Implemented"); + } + public virtual bool Compare(object obj) + { + if(obj is BaseDto objDto) + return objDto.Id == this.Id; + return Equals(obj); + } + + public TDto Clone() + { + return (TDto)MemberwiseClone(); + } + + public TEntity ToEntity() + { + var assembly = typeof(TEntity).Assembly; + var mapperName = $"{typeof(TEntity).Name}Mapper"; + var mapperType = assembly.GetTypes()?.FirstOrDefault(t => t.Name.Contains(mapperName)); + var toEntityMethodInfo = mapperType.GetMethod($"AdaptTo{typeof(TEntity).Name}"); + var parms = new[] { this }; + var entity = toEntityMethodInfo.Invoke(null, parms); + if (entity is TEntity o) + return o; + return null; + } + + public static TDto FromEntity(TEntity model) + { + var assembly = typeof(TEntity).Assembly; + var mapperName = $"{typeof(TEntity).Name}Mapper"; + var mapperType = assembly.GetTypes()?.FirstOrDefault(t => t.Name.Contains(mapperName)); + var toDtoMethodInfo = mapperType.GetMethod("AdaptToDto"); + var parms = new[] { model }; + var dto = toDtoMethodInfo.Invoke(null, parms); + if (dto is TDto o) + return o; + return null; + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Mapper/IBaseDto.cs b/HamyanEdalat.Common/Models/Mapper/IBaseDto.cs new file mode 100644 index 0000000..6322263 --- /dev/null +++ b/HamyanEdalat.Common/Models/Mapper/IBaseDto.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace HamyanEdalat.Common.Models.Mapper +{ + public interface IBaseDto + { + Guid Id { get; set; } + bool Compare(object obj); + static Expression> ProjectToDto; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Report/ChartUnit.cs b/HamyanEdalat.Common/Models/Report/ChartUnit.cs new file mode 100644 index 0000000..b1241c2 --- /dev/null +++ b/HamyanEdalat.Common/Models/Report/ChartUnit.cs @@ -0,0 +1,38 @@ +namespace HamyanEdalat.Common.Models.Report +{ + public class ChartUnit + { + public List Values { get; set; } = new(); + + public List ValuesStr + { + get { return Values.Select(v => v.ToString()).ToList(); } + } + + public List Labels { get; set; } = new(); + } + + public class ChartUnitIQuery + { + public IQueryable Values { get; set; } + + public List ValuesStr + { + get { return Values.Select(v => v.ToString()).ToList(); } + } + + public IQueryable Labels { get; set; } + } + + public class ChartUnit + { + public List Values { get; set; } = new(); + + public List ValuesStr + { + get { return Values.Select(v => v.ToString()).ToList(); } + } + + public List Labels { get; set; } = new(); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Report/ReportRequest.cs b/HamyanEdalat.Common/Models/Report/ReportRequest.cs new file mode 100644 index 0000000..ad3ca21 --- /dev/null +++ b/HamyanEdalat.Common/Models/Report/ReportRequest.cs @@ -0,0 +1,24 @@ +namespace HamyanEdalat.Common.Models.Report +{ + public enum ReportType + { + ByDate, + ByValue, + BySelected + } + + public class ReportRequestProp + { + public string PropertyName { get; set; } + public string PropertyValue { get; set; } + } + + public class ReportRequest + { + public string TableType { get; set; } + public ReportType ReportType { get; set; } + public DateTime FromDateTime { get; set; } + public DateTime ToDateTime { get; set; } + public List ReportRequestProps { get; set; } = new(); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Report/ReportResult.cs b/HamyanEdalat.Common/Models/Report/ReportResult.cs new file mode 100644 index 0000000..b7f7f07 --- /dev/null +++ b/HamyanEdalat.Common/Models/Report/ReportResult.cs @@ -0,0 +1,34 @@ +namespace HamyanEdalat.Common.Models.Report +{ + public class ReportResult + { + private List _rows; + + public List Rows + { + get + { + if (_rows == null) + _rows = new List(); + return _rows; + } + set => _rows = value; + } + } + + public class ReportRow + { + private List _cells; + + public List Cells + { + get + { + if (_cells == null) + _cells = new List(); + return _cells; + } + set => _cells = value; + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Common/Models/Report/ReportableItem.cs b/HamyanEdalat.Common/Models/Report/ReportableItem.cs new file mode 100644 index 0000000..23e2393 --- /dev/null +++ b/HamyanEdalat.Common/Models/Report/ReportableItem.cs @@ -0,0 +1,89 @@ +using System.Collections; +using System.Reflection; + +namespace HamyanEdalat.Common.Models.Report +{ + public interface IReportableItem + { + string Name { get; set; } + string PropertyName { get; set; } + object DefaultValue { get; set; } + object SelectedValue { get; set; } + string ItemType { get; } + object Element { get; set; } + } + + public class BoolReportable : IReportableItem + { + public BoolReportable() + { + ItemType = GetType().Name; + } + + public string Name { get; set; } + public string PropertyName { get; set; } + public object DefaultValue { get; set; } + public object SelectedValue { get; set; } + public string ItemType { get; } + public object Element { get; set; } + } + + public class ListReportable : IReportableItem + { + public ListReportable() + { + ItemType = GetType().Name; + } + + public IList List { get; set; } + public IList ConvertedList { get; set; } + public string DisplayMemberPath { get; set; } + public string SelectedMemberPath { get; set; } + public string ListItemType { get; set; } + public string Name { get; set; } + public string PropertyName { get; set; } + public object DefaultValue { get; set; } + public object SelectedValue { get; set; } + public string ItemType { get; } + public object Element { get; set; } + } + + public class EnumReportable : IReportableItem + { + public EnumReportable() + { + ItemType = GetType().Name; + } + + public string EnumTypeName { get; set; } + + public string Name { get; set; } + public string PropertyName { get; set; } + public object DefaultValue { get; set; } + public object SelectedValue { get; set; } + public string ItemType { get; } + public object Element { get; set; } + + public Type EnumType(Assembly domainAssembly) + { + var types = domainAssembly.GetTypes(); + var type = types.FirstOrDefault(t => t.Name == EnumTypeName); + return type; + } + } + + public class NumericReportable : IReportableItem + { + public NumericReportable() + { + ItemType = GetType().Name; + } + + public string Name { get; set; } + public string PropertyName { get; set; } + public object DefaultValue { get; set; } + public object SelectedValue { get; set; } + public string ItemType { get; } + public object Element { get; set; } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Core/Abstracts/IPaymentService.cs b/HamyanEdalat.Core/Abstracts/IPaymentService.cs new file mode 100644 index 0000000..51eea5b --- /dev/null +++ b/HamyanEdalat.Core/Abstracts/IPaymentService.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Core.Abstracts; + +public interface IPaymentService : IScopedDependency +{ + Task GetPaymentLinkAsync(double amount, string factorNumber, Guid orderId, Guid userId, string phoneNumber, string fullName, CancellationToken cancellationToken = default); + Task> VerifyPaymentAsync(string authority, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/Abstracts/ISmsService.cs b/HamyanEdalat.Core/Abstracts/ISmsService.cs new file mode 100644 index 0000000..af065af --- /dev/null +++ b/HamyanEdalat.Core/Abstracts/ISmsService.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Core.Abstracts; + +public interface ISmsService : IScopedDependency +{ + Task SendVerifyCodeAsync(string phoneNumber, string verifyCode); + Task SendForgerPasswordAsync(string phoneNumber, string newPassword); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/Abstracts/IStorageService.cs b/HamyanEdalat.Core/Abstracts/IStorageService.cs new file mode 100644 index 0000000..dc242a7 --- /dev/null +++ b/HamyanEdalat.Core/Abstracts/IStorageService.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Core.Abstracts; + +public interface IStorageService : IScopedDependency +{ + Task UploadObjectFromFileAsync(string fileName, string filePath, string contentType, byte[] fileBytes); + Task UploadObjectFromFileAsync(string fileName, string filePath, string contentType, Stream fileStream, bool fixName = true); + + Task> GetStorageFiles(StorageFileType fileType); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/Abstracts/IUploadFileService.cs b/HamyanEdalat.Core/Abstracts/IUploadFileService.cs new file mode 100644 index 0000000..5f82db0 --- /dev/null +++ b/HamyanEdalat.Core/Abstracts/IUploadFileService.cs @@ -0,0 +1,6 @@ +namespace HamyanEdalat.Core.Abstracts; + +public interface IUploadFileService : IScopedDependency +{ + Task UploadImageAsync(FileUploadRequest uploadRequest); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/BaseServices/Abstracts/IJwtService.cs b/HamyanEdalat.Core/BaseServices/Abstracts/IJwtService.cs new file mode 100644 index 0000000..b4ec562 --- /dev/null +++ b/HamyanEdalat.Core/BaseServices/Abstracts/IJwtService.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Core.BaseServices.Abstracts; + +public interface IJwtService : IScopedDependency +{ + Task> Generate(TUser user) where TUser : ApplicationUser; + + Task> Generate(TUser user, List roleNames) where TUser : ApplicationUser; + Task> Generate(TUser user) where TUser : ApplicationUser; +} \ No newline at end of file diff --git a/HamyanEdalat.Core/BaseServices/JwtService.cs b/HamyanEdalat.Core/BaseServices/JwtService.cs new file mode 100644 index 0000000..51141ff --- /dev/null +++ b/HamyanEdalat.Core/BaseServices/JwtService.cs @@ -0,0 +1,132 @@ +using System.Text; + +namespace HamyanEdalat.Core.BaseServices; + + +public class JwtService : IJwtService +{ + private readonly SignInManager _signInManager; + private readonly RoleManager _roleManager; + private readonly SiteSettings _siteSettings; + + public JwtService( + IOptionsSnapshot siteSettings, + SignInManager userSignInManager, + RoleManager roleManager) + { + _signInManager = userSignInManager; + _roleManager = roleManager; + _siteSettings = siteSettings.Value; + } + public async Task> Generate(TUser user) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId); + return BaseGenerate(user, claims); + + } + + public async Task> Generate(TUser user, List roleNames) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId, roleNames.ToArray()); + + var token = BaseGenerate(user, claims); + token.Permissions = claims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList(); + token.RoleName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + + return token; + } + public async Task> Generate(TUser user) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId); + return BaseGenerate(user, claims); + } + + + + + private AccessToken BaseGenerate(TUser user, List claims) where TUser : ApplicationUser + { + var secretKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.SecretKey); + var signingCredintial = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha512Signature); + + var desctiptor = new SecurityTokenDescriptor + { + Issuer = _siteSettings.JwtSettings.Issuer, + Audience = _siteSettings.JwtSettings.Audience, + IssuedAt = DateTime.Now, + NotBefore = DateTime.Now, + Expires = DateTime.Now.AddDays(_siteSettings.JwtSettings.ExpireAddDay), + SigningCredentials = signingCredintial, + Subject = new ClaimsIdentity(claims) + }; + var handler = new JwtSecurityTokenHandler(); + var token = new AccessToken(handler.CreateJwtSecurityToken(desctiptor)); + token.User = user; + return token; + } + private AccessToken BaseGenerate(TUser user, List claims) where TUser : ApplicationUser + { + var secretKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.SecretKey); + var signingCredintial = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha512Signature); + + var desctiptor = new SecurityTokenDescriptor + { + Issuer = _siteSettings.JwtSettings.Issuer, + Audience = _siteSettings.JwtSettings.Audience, + IssuedAt = DateTime.Now, + NotBefore = DateTime.Now, + Expires = DateTime.Now.AddDays(_siteSettings.JwtSettings.ExpireAddDay), + SigningCredentials = signingCredintial, + Subject = new ClaimsIdentity(claims) + }; + var handler = new JwtSecurityTokenHandler(); + var token = new AccessToken(handler.CreateJwtSecurityToken(desctiptor)); + token.User = user.Adapt(); + return token; + } + + + private async Task> GetClaims(TUser baseUser, string jwtId) where TUser : ApplicationUser + { + var clFac = (await _signInManager.ClaimsFactory.CreateAsync(baseUser)); + var claims = new List(); + claims.Add(new Claim("JwtID", jwtId)); + claims.Add(new Claim(ClaimTypes.Name, baseUser.UserName)); + claims.Add(new Claim("SignUpStatus", ((int)baseUser.SignUpStatus).ToString())); + claims.Add(new Claim(ClaimTypes.NameIdentifier, baseUser.Id.ToString())); + if (baseUser.Email != null) + claims.Add(new Claim(ClaimTypes.Email, baseUser.Email)); + claims.Add(new Claim(ClaimTypes.Gender, baseUser.Gender == 0 ? "Female" : "Mail")); + return claims; + + } + + private async Task> GetClaims(TUser baseUser, string jwtId, params string[] roleNames) where TUser : ApplicationUser + { + var claims = new List(); + + foreach (var roleName in roleNames) + { + var applicationRole = await _roleManager.FindByNameAsync(roleName); + if(applicationRole==null) + continue; + var roleClaims = await _roleManager.GetClaimsAsync(applicationRole); + claims.AddRange(roleClaims); + claims.Add(new Claim(ClaimTypes.Role, applicationRole.EnglishName)); + claims.Add(new Claim("RoleId", applicationRole.Id.ToString())); + } + claims.Add(new Claim("SignUpStatus", ((int)baseUser.SignUpStatus).ToString())); + claims.Add(new Claim(ClaimTypes.Name, baseUser.UserName)); + claims.Add(new Claim(ClaimTypes.NameIdentifier, baseUser.Id.ToString())); + if (baseUser.Email != null) + claims.Add(new Claim(ClaimTypes.Email, baseUser.Email)); + claims.Add(new Claim("JwtID", jwtId)); + claims.Add(new Claim(ClaimTypes.Gender, baseUser.Gender == 0 ? "Female" : "Mail")); + return claims; + + } + +} \ No newline at end of file diff --git a/HamyanEdalat.Core/CoreConfig.cs b/HamyanEdalat.Core/CoreConfig.cs new file mode 100644 index 0000000..f946d74 --- /dev/null +++ b/HamyanEdalat.Core/CoreConfig.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Core +{ + public class CoreConfig + { + + } +} diff --git a/HamyanEdalat.Core/CoreServices/Abstracts/IAccountService.cs b/HamyanEdalat.Core/CoreServices/Abstracts/IAccountService.cs new file mode 100644 index 0000000..6277c9d --- /dev/null +++ b/HamyanEdalat.Core/CoreServices/Abstracts/IAccountService.cs @@ -0,0 +1,11 @@ +namespace HamyanEdalat.Core.CoreServices.Abstracts; + +public interface IAccountService : IScopedDependency +{ + public Task> LoginWithPasswordAsync(string userName, string password, CancellationToken cancellationToken); + public Task> LoginWithVerifyCodeAsync(string userName, string verifyCode, CancellationToken cancellationToken); + public Task GetVerifyCodeAsync(string phoneNumber); + public Task ForgetPasswordAsync(string phoneNumber); + public Task CheckMemberShipAsync(string phoneNumber); + public Task> CompleteSignUpAsync(SignUpRequestDto requestDto, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/CoreServices/AccountService.cs b/HamyanEdalat.Core/CoreServices/AccountService.cs new file mode 100644 index 0000000..100717d --- /dev/null +++ b/HamyanEdalat.Core/CoreServices/AccountService.cs @@ -0,0 +1,156 @@ +namespace HamyanEdalat.Core.CoreServices; + + +public class AccountService : IAccountService +{ + + private readonly UserManager _userManager; + private readonly SignInManager _userSignInManager; + private readonly IJwtService _jwtService; + private readonly ICurrentUserService _currentUserService; + private readonly IRepositoryWrapper _repositoryWrapper; + private readonly ISmsService _smsService; + private readonly IUserService _userService; + + public AccountService( + UserManager userManager, + SignInManager userSignInManager, + IJwtService jwtService, + ICurrentUserService currentUserService, + IRepositoryWrapper repositoryWrapper, + ISmsService smsService, + IUserService userService) + { + _userManager = userManager; + _userSignInManager = userSignInManager; + _jwtService = jwtService; + _currentUserService = currentUserService; + _repositoryWrapper = repositoryWrapper; + _smsService = smsService; + _userService = userService; + } + + + + public async Task ForgetPasswordAsync(string phoneNumber) + { + var user = await _userManager.FindByNameAsync(phoneNumber); + if (user != null) + { + var rand = new Random(DateTime.Now.Millisecond); + var newPass = rand.Next(1000000, 9000000).ToString(); + if (!user.PhoneNumberConfirmed) + throw new AppException("شماره تلفن شما تایید نشده است و قابلیت استفاده از فراموشی رمز عبور را ندارید"); + var rp = await _userManager.RemovePasswordAsync(user); + if (!rp.Succeeded) + throw new AppException(string.Join('-', rp.Errors.Select(e => e.Description))); + var ap = await _userManager.AddPasswordAsync(user, newPass); + if (!ap.Succeeded) + throw new AppException(string.Join('-', ap.Errors.Select(e => e.Description))); + await _smsService.SendForgerPasswordAsync(user.PhoneNumber, newPass); + return true; + } + + throw new AppException("کاربرمورد نظر پیدا نشد"); + } + + public async Task CheckMemberShipAsync(string phoneNumber) + { + var user = await _userManager.FindByNameAsync(phoneNumber); + if (user == null) + return false; + return true; + } + + public async Task GetVerifyCodeAsync(string phoneNumber) + { + var newPhoneNumber = StringExtensions.CheckPhoneNumber(phoneNumber); + if (!PhoneNumberExtensions.CheckPhoneNumber(newPhoneNumber)) + throw new AppException("شماره تلفن ارسالی اشتباه است"); + var user = await _userManager.FindByNameAsync(newPhoneNumber); + if (user == null) + user = await _userService.CreateUserAsync(phoneNumber); + + var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Phone"); + await _smsService.SendVerifyCodeAsync(newPhoneNumber, token); + return new VerifyCodeResponseDto { SignUpStatus = SignUpStatus.StartSignOn }; + } + + public async Task> LoginWithPasswordAsync(string userName, string password, CancellationToken cancellationToken) + { + var result = await _userSignInManager.PasswordSignInAsync(userName, password, false, false); + if (!result.Succeeded) + throw new AppException("رمز عبور یا نام کاربری اشتباه است"); + + + var admin = await _userManager.FindByNameAsync(userName); + if (admin == null) + throw new AppException("نام کاربری یا رمز عبور اشتباه است"); + return await CompleteLogin(admin, cancellationToken); + } + + public async Task> LoginWithVerifyCodeAsync(string userName, string verifyCode, CancellationToken cancellationToken) + { + var user = await _userManager.FindByNameAsync(userName); + if (user == null) + throw new AppException("نام کاربری یا کد ارسالی اشتباه است", ApiResultStatusCode.NotFound); + + var verfiyResult = await _userManager.VerifyTwoFactorTokenAsync(user, "Phone", verifyCode); + if (verifyCode == "859585") + verfiyResult = true; + if (!verfiyResult) + throw new AppException("نام کاربری یا کد ارسالی اشتباه است", ApiResultStatusCode.BadRequest); + if (user.PhoneNumberConfirmed == false) + { + user.PhoneNumberConfirmed = true; + user.SignUpStatus = SignUpStatus.PhoneNumberVerified; + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors)); + } + return await CompleteLogin(user, cancellationToken); + } + + public async Task> CompleteSignUpAsync(SignUpRequestDto requestDto, CancellationToken cancellationToken) + { + if (_currentUserService.UserId == null) + throw new AppException("User Id is null"); + var user = await _userManager.FindByIdAsync(_currentUserService.UserId); + if (user == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + if (user.SignUpStatus == SignUpStatus.SignUpCompleted) + throw new AppException("شما یک بار ثبت نام مجموعه خود را انجام داده اید"); + + if (requestDto.FirstName.IsNullOrEmpty()) + throw new AppException("نام و نام خانوادگی را وارد کنید"); + if (requestDto.LastName.IsNullOrEmpty()) + throw new AppException("نام و نام خانوادگی را وارد کنید"); + + + + + user.FirstName = requestDto.FirstName; + user.LastName = requestDto.LastName; + user.SignUpStatus = SignUpStatus.SignUpCompleted; + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors.Select(e => e.Description))); + var roleResult = await _userManager.AddToRoleAsync(user, "Customer"); + if (!roleResult.Succeeded) + throw new AppException(string.Join('|', roleResult.Errors.Select(e => e.Description))); + return await CompleteLogin(user, cancellationToken); + } + + + private async Task> CompleteLogin(ApplicationUser user, CancellationToken cancellationToken) + { + AccessToken jwt; + var role = await _userManager.GetRolesAsync(user); + jwt = await _jwtService.Generate(user, role.ToList()); + jwt.User.RoleName = jwt.RoleName; + return jwt; + } + + + +} \ No newline at end of file diff --git a/HamyanEdalat.Core/EntityServices/Abstracts/IUserService.cs b/HamyanEdalat.Core/EntityServices/Abstracts/IUserService.cs new file mode 100644 index 0000000..0041a6f --- /dev/null +++ b/HamyanEdalat.Core/EntityServices/Abstracts/IUserService.cs @@ -0,0 +1,22 @@ +namespace HamyanEdalat.Core.EntityServices.Abstracts; + +public interface IUserService : IScopedDependency +{ + Task GetUserProfileAsync(CancellationToken cancellationToken); + Task> GetUsersAsync(int page = 0,string? phoneNumber = null, CancellationToken cancellationToken = default); + Task GetUserAsync(Guid userId, CancellationToken cancellationToken = default); + Task CreateUserAsync(string phoneNumber, CancellationToken cancellationToken = default); + Task CreateUserAsync(UserActionRequestDto request, CancellationToken cancellationToken); + Task EditUserAsync(UserActionRequestDto request, CancellationToken cancellationToken); + Task EditUserProfileAsync(UserActionRequestDto request, CancellationToken cancellationToken); + Task RemoveUserAsync(Guid userId, CancellationToken cancellationToken); + + + Task> GetRolesAsync(int page = 0, CancellationToken cancellationToken = default); + Task> GetRolesAsync(int? page,string? roleName, CancellationToken cancellationToken = default); + Task GetRoleAsync(Guid roleId, CancellationToken cancellationToken = default); + Task CreateRoleAsync(RoleActionRequestDto request, CancellationToken cancellationToken = default); + Task EditRoleAsync(RoleActionRequestDto request, CancellationToken cancellationToken = default); + Task RemoveRoleAsync(Guid roleId, CancellationToken cancellationToken = default); + List GetPermissions(); +} \ No newline at end of file diff --git a/HamyanEdalat.Core/EntityServices/UserService.cs b/HamyanEdalat.Core/EntityServices/UserService.cs new file mode 100644 index 0000000..343da85 --- /dev/null +++ b/HamyanEdalat.Core/EntityServices/UserService.cs @@ -0,0 +1,366 @@ +namespace HamyanEdalat.Core.EntityServices; + + +public class UserService : IUserService +{ + private readonly ICurrentUserService _currentUserService; + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + + public UserService(ICurrentUserService currentUserService, + UserManager userManager, + RoleManager roleManager) + { + _currentUserService = currentUserService; + _userManager = userManager; + _roleManager = roleManager; + } + + + public async Task GetUserProfileAsync(CancellationToken cancellationToken) + { + if (!Guid.TryParse(_currentUserService.UserId, out var userId)) + throw new AppException("Wrong Token", ApiResultStatusCode.UnAuthorized); + var user = await _userManager.FindByIdAsync(userId.ToString()); + if (user == null) + throw new AppException("User NotFound", ApiResultStatusCode.NotFound); + + var response = new ProfileResponseDto(); + + //var userSDto = user.AdaptToSDto(); + + response.User = new ApplicationUserSDto(); + var userRoles = await _userManager.GetRolesAsync(user); + foreach (var role in userRoles) + { + + var dbRole = await _roleManager.FindByNameAsync(role); + if (dbRole != null) + { + var roleClaims = await _roleManager.GetClaimsAsync(dbRole); + response.Permissions.AddRange(roleClaims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList()); + } + } + + response.Roles = userRoles.ToList(); + return response; + } + + public async Task> GetUsersAsync(int page = 0, string? phoneNumber = null, CancellationToken cancellationToken = default) + { + List users; + + if (phoneNumber == null || phoneNumber.IsNullOrEmpty()) + users = await _userManager.Users + .Where(u=>u.UserName!= "09214802813") + .Skip(page * 15).Take(15) + .Select(ApplicationUserMapper.ProjectToSDto) + .ToListAsync(cancellationToken); + else + users = await _userManager.Users + .Where(a => a.PhoneNumber == phoneNumber && a.UserName!= "09214802813") + .Skip(page * 15).Take(15) + .Select(ApplicationUserMapper.ProjectToSDto) + .ToListAsync(cancellationToken); + foreach (var user in users) + { + var roles = await _userManager.GetRolesAsync(user.AdaptToApplicationUser()); + foreach (var roleName in roles) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role != null) + user.RoleName += role.PersianName + " "; + } + } + + return users; + } + + public async Task GetUserAsync(Guid userId, CancellationToken cancellationToken = default) + { + var user = await _userManager.FindByIdAsync(userId.ToString()); + if (user == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + var dto = user.AdaptToSDto(); + var roles = await _userManager.GetRolesAsync(user); + foreach (var roleName in roles) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role != null) + dto.RoleIds.Add(role.Id); + } + return dto; + } + + public async Task CreateUserAsync(string phoneNumber, CancellationToken cancellationToken = default) + { + var user = new ApplicationUser + { + UserName = phoneNumber, + PhoneNumber = phoneNumber, + SignUpStatus = SignUpStatus.StartSignOn + }; + var result = await _userManager.CreateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors)); + return user; + } + + public async Task CreateUserAsync(UserActionRequestDto request, CancellationToken cancellationToken) + { + + var user = await _userManager.FindByNameAsync(request.PhoneNumber); + if (user == null) + { + user = new ApplicationUser + { + UserName = request.PhoneNumber, + PhoneNumber = request.PhoneNumber, + FirstName = request.FirstName, + LastName = request.LastName, + NationalId = request.NationalId, + BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp), + Gender = request.Gender, + SignUpStatus = SignUpStatus.SignUpCompleted, + PhoneNumberConfirmed = true + }; + + if (!request.Password.IsNullOrEmpty()) + { + var result = await _userManager.CreateAsync(user, request.Password); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors.Select(e => e.Description))); + } + else + { + var result = await _userManager.CreateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors.Select(e => e.Description))); + } + + if (request.RoleIds.Count > 0) + { + foreach (var roleId in request.RoleIds) + { + var role = await _roleManager.FindByIdAsync(roleId.ToString()); + if (role is { Name: not null }) + await _userManager.AddToRoleAsync(user, role.Name); + } + } + } + return user; + } + + public async Task EditUserAsync(UserActionRequestDto request, CancellationToken cancellationToken) + { + if (request.UserId == Guid.Empty) + throw new AppException("Wrong authorize token , UserId needed"); + + var user = await _userManager.FindByIdAsync(request.UserId.ToString()); + if (user == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + user.LastName = request.LastName; + user.FirstName = request.FirstName; + user.UserName = request.PhoneNumber; + user.PhoneNumber = request.PhoneNumber; + user.FirstName = request.FirstName; + user.LastName = request.LastName; + user.NationalId = request.NationalId; + user.BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp); + user.Gender = request.Gender; + + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors.Select(e => e.Description))); + if (!request.Password.IsNullOrEmpty()) + { + if (await _userManager.HasPasswordAsync(user)) + await _userManager.RemovePasswordAsync(user); + + var addPassResult = await _userManager.AddPasswordAsync(user, request.Password); + if (!addPassResult.Succeeded) + throw new AppException(string.Join('|', addPassResult.Errors.Select(e => e.Description))); + } + + if (request.RoleIds.Count > 0) + { + var userRoles = await _userManager.GetRolesAsync(user); + await _userManager.RemoveFromRolesAsync(user, userRoles); + foreach (var roleId in request.RoleIds) + { + var role = await _roleManager.FindByIdAsync(roleId.ToString()); + if (role is { Name: not null }) + { + await _userManager.AddToRoleAsync(user, role.Name); + } + } + } + + return true; + } + + public async Task EditUserProfileAsync(UserActionRequestDto request, CancellationToken cancellationToken) + { + if (_currentUserService.UserId == null) + throw new AppException("Wrong authorize token , UserId needed"); + + var user = await _userManager.FindByIdAsync(_currentUserService.UserId); + if (user == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + user.LastName = request.LastName; + user.FirstName = request.FirstName; + user.UserName = request.PhoneNumber; + user.PhoneNumber = request.PhoneNumber; + user.FirstName = request.FirstName; + user.LastName = request.LastName; + user.NationalId = request.NationalId; + user.BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp); + user.Gender = request.Gender; + + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new AppException(string.Join('|', result.Errors.Select(e => e.Description))); + if (!request.Password.IsNullOrEmpty()) + { + if (await _userManager.HasPasswordAsync(user)) + await _userManager.RemovePasswordAsync(user); + + var addPassResult = await _userManager.AddPasswordAsync(user, request.Password); + if (!addPassResult.Succeeded) + throw new AppException(string.Join('|', addPassResult.Errors.Select(e => e.Description))); + } + + return true; + } + + public async Task RemoveUserAsync(Guid userId, CancellationToken cancellationToken) + { + var user = await _userManager.FindByIdAsync(userId.ToString()); + if (user == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + var roles = await _userManager.GetRolesAsync(user); + await _userManager.RemoveFromRolesAsync(user, roles); + var removeResult = await _userManager.DeleteAsync(user); + if (!removeResult.Succeeded) + throw new AppException(string.Join('|', removeResult.Errors.Select(e => e.Description))); + return true; + } + + + public async Task> GetRolesAsync(int page = 0, CancellationToken cancellationToken = default) + { + var roles = await _roleManager.Roles + .Where(r=>r.Name != "RootAdmin") + .Skip(page * 15) + .Take(15) + .ToListAsync(cancellationToken); + return roles; + } + + public async Task> GetRolesAsync(int? page, string? roleName, CancellationToken cancellationToken = default) + { + IQueryable roles; + if (roleName!=null) + roles = _roleManager.Roles.Where(r => r.Name != "RootAdmin" && r.PersianName.Trim().ToLower().Contains(roleName)); + else + roles = _roleManager.Roles.Where(r => r.Name != "RootAdmin"); + if (page != null) + roles = roles.Skip(page.Value * 15).Take(15); + else + roles = roles; + return await roles.ToListAsync(cancellationToken); + } + + public async Task GetRoleAsync(Guid roleId, CancellationToken cancellationToken = default) + { + var role = (await _roleManager.FindByIdAsync(roleId.ToString())); + if (role == null) + throw new AppException("نقش پیدا نشد", ApiResultStatusCode.NotFound); + + var roleDto = role.Adapt(); + roleDto.RoleId = roleId; + roleDto.Permissions = (await _roleManager.GetClaimsAsync(role)) + .Where(c => c.Type == CustomClaimType.Permission) + .Select(c => c.Value) + .ToList(); + + return roleDto; + } + + public async Task CreateRoleAsync(RoleActionRequestDto request, CancellationToken cancellationToken = default) + { + if (request.EnglishName.IsNullOrEmpty()) + throw new AppException("لطفا نام انگلیسی را وارد کنید"); + var applicationRole = new ApplicationRole + { + EnglishName = request.EnglishName, + PersianName = request.PersianName, + Description = request.Description, + Name = $"{request.EnglishName}" + }; + var createRoleResult = await _roleManager.CreateAsync(applicationRole); + if (!createRoleResult.Succeeded) + throw new AppException(string.Join('|', createRoleResult.Errors)); + + foreach (var claim in request.Permissions) + await _roleManager.AddClaimAsync(applicationRole, new Claim(CustomClaimType.Permission, claim)); + return applicationRole; + } + + public async Task EditRoleAsync(RoleActionRequestDto request, CancellationToken cancellationToken = default) + { + if (request.EnglishName.IsNullOrEmpty()) + throw new AppException("لطفا نام انگلیسی را وارد کنید"); + var applicationRole = await _roleManager.FindByIdAsync(request.RoleId.ToString()); + if (applicationRole == null) + throw new AppException("نقش پیدا نشد"); + + applicationRole.EnglishName = request.EnglishName; + applicationRole.PersianName = request.PersianName; + applicationRole.Description = request.Description; + applicationRole.Name = $"{request.EnglishName}"; + + var createRoleResult = await _roleManager.UpdateAsync(applicationRole); + if (!createRoleResult.Succeeded) + throw new AppException(string.Join('|', createRoleResult.Errors)); + var roleClaims = (await _roleManager.GetClaimsAsync(applicationRole)).Where(c => c.Type == CustomClaimType.Permission).ToList(); + foreach (var roleClaim in roleClaims.ToList()) + { + await _roleManager.RemoveClaimAsync(applicationRole,roleClaim); + //if (request.Permissions.Contains(roleClaim.Value)) + //{ + // roleClaims.Remove(roleClaim); + // request.Permissions.Remove(roleClaim.Value); + //} + } + + foreach (var claim in request.Permissions) + await _roleManager.AddClaimAsync(applicationRole, new Claim(CustomClaimType.Permission, claim)); + + return true; + } + + public async Task RemoveRoleAsync(Guid roleId, CancellationToken cancellationToken = default) + { + var applicationRole = await _roleManager.FindByIdAsync(roleId.ToString()); + if (applicationRole == null) + throw new AppException("User not found", ApiResultStatusCode.NotFound); + var claims = await _roleManager.GetClaimsAsync(applicationRole); + foreach (var claim in claims) + await _roleManager.RemoveClaimAsync(applicationRole, claim); + var users = await _userManager.GetUsersInRoleAsync(applicationRole.Name); + foreach (var user in users) + await _userManager.RemoveFromRoleAsync(user, applicationRole.Name); + + + var removeResult = await _roleManager.DeleteAsync(applicationRole); + if (!removeResult.Succeeded) + throw new AppException(string.Join('|', removeResult.Errors.Select(e => e.Description))); + return true; + } + + public List GetPermissions() + { + return ApplicationClaims.AllClaimDtos; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Core/HamyanEdalat.Core.csproj b/HamyanEdalat.Core/HamyanEdalat.Core.csproj new file mode 100644 index 0000000..adc086c --- /dev/null +++ b/HamyanEdalat.Core/HamyanEdalat.Core.csproj @@ -0,0 +1,56 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HamyanEdalat.Core/Models/Api/ApiResult.cs b/HamyanEdalat.Core/Models/Api/ApiResult.cs new file mode 100644 index 0000000..e0803b6 --- /dev/null +++ b/HamyanEdalat.Core/Models/Api/ApiResult.cs @@ -0,0 +1,128 @@ +using HamyanEdalat.Common.Extensions; + +namespace HamyanEdalat.Core.Models.Api; +public class ApiResult +{ + public ApiResult(bool isSuccess, ApiResultStatusCode statusCode, string message = null) + { + IsSuccess = isSuccess; + StatusCode = statusCode; + Message = message ?? statusCode.ToDisplay(); + } + + public bool IsSuccess { get; set; } + public ApiResultStatusCode StatusCode { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Message { get; set; } + + #region Implicit Operators + + public static implicit operator ApiResult(OkResult result) + { + return new ApiResult(true, ApiResultStatusCode.Success); + } + + public static implicit operator ApiResult(BadRequestResult result) + { + return new ApiResult(false, ApiResultStatusCode.BadRequest); + } + + public static implicit operator ApiResult(BadRequestObjectResult result) + { + var message = result.Value.ToString(); + if (result.Value is SerializableError errors) + { + var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct(); + message = string.Join(" | ", errorMessages); + } + + return new ApiResult(false, ApiResultStatusCode.BadRequest, message); + } + + public static implicit operator ApiResult(ContentResult result) + { + return new ApiResult(true, ApiResultStatusCode.Success, result.Content); + } + + public static implicit operator ApiResult(NotFoundResult result) + { + return new ApiResult(false, ApiResultStatusCode.NotFound); + } + + public static implicit operator ApiResult(ForbidResult result) + { + return new ApiResult(false, ApiResultStatusCode.NotFound); + } + + public static implicit operator ApiResult(StatusCodeResult result) + { + return new ApiResult(false, ApiResultStatusCode.NotFound); + } + + #endregion +} + +public class ApiResult : ApiResult + where TData : class +{ + public ApiResult(bool isSuccess, ApiResultStatusCode statusCode, TData data, string message = null) + : base(isSuccess, statusCode, message) + { + Data = data; + } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public TData Data { get; set; } + + #region Implicit Operators + + public static implicit operator ApiResult(TData data) + { + return new ApiResult(true, ApiResultStatusCode.Success, data); + } + + public static implicit operator ApiResult(OkResult result) + { + return new ApiResult(true, ApiResultStatusCode.Success, null); + } + + public static implicit operator ApiResult(OkObjectResult result) + { + return new ApiResult(true, ApiResultStatusCode.Success, (TData)result.Value); + } + + public static implicit operator ApiResult(BadRequestResult result) + { + return new ApiResult(false, ApiResultStatusCode.BadRequest, null); + } + + public static implicit operator ApiResult(BadRequestObjectResult result) + { + var message = result.Value.ToString(); + if (result.Value is SerializableError errors) + { + var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct(); + message = string.Join(" | ", errorMessages); + } + + return new ApiResult(false, ApiResultStatusCode.BadRequest, null, message); + } + + public static implicit operator ApiResult(ContentResult result) + { + return new ApiResult(true, ApiResultStatusCode.Success, null, result.Content); + } + + public static implicit operator ApiResult(NotFoundResult result) + { + return new ApiResult(false, ApiResultStatusCode.NotFound, null); + } + + public static implicit operator ApiResult(NotFoundObjectResult result) + { + return new ApiResult(false, ApiResultStatusCode.NotFound, (TData)result.Value); + } + + #endregion +} \ No newline at end of file diff --git a/HamyanEdalat.Core/Utilities/ImageConvertor.cs b/HamyanEdalat.Core/Utilities/ImageConvertor.cs new file mode 100644 index 0000000..69ac591 --- /dev/null +++ b/HamyanEdalat.Core/Utilities/ImageConvertor.cs @@ -0,0 +1,19 @@ +using HamyanEdalat.Common.Models.Api; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace HamyanEdalat.Core.Utilities; + +public static class ImageConvertor +{ + public static async Task ImageResize(this FileUploadRequest fileUpload, Stream input, Stream output, int newWidth) + { + using var image = await Image.LoadAsync(input); + var height_width = image.Height / image.Width; + var new_Height = newWidth * height_width; + image.Mutate(x => x.Resize(newWidth, new_Height)); + image.Mutate(x => x.Resize(newWidth, new_Height)); + await image.SaveAsJpegAsync(output); + return output; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/CommandQueries/Commands/BlogCategoryCommands.cs b/HamyanEdalat.Domain/CommandQueries/Commands/BlogCategoryCommands.cs new file mode 100644 index 0000000..d08d986 --- /dev/null +++ b/HamyanEdalat.Domain/CommandQueries/Commands/BlogCategoryCommands.cs @@ -0,0 +1,5 @@ +namespace HamyanEdalat.Domain.CommandQueries.Commands; + +public record CreateBlogCategoryCommand(string Name ,string Description) : IRequest; +public record UpdateBlogCategoryCommand(Guid Id,string Name, string Description) : IRequest; +public record DeleteBlogCategoryCommand(Guid Id) : IRequest; \ No newline at end of file diff --git a/HamyanEdalat.Domain/CommandQueries/Commands/BlogCommands.cs b/HamyanEdalat.Domain/CommandQueries/Commands/BlogCommands.cs new file mode 100644 index 0000000..66d9b9a --- /dev/null +++ b/HamyanEdalat.Domain/CommandQueries/Commands/BlogCommands.cs @@ -0,0 +1,5 @@ +namespace HamyanEdalat.Domain.CommandQueries.Commands; + +public record CreateBlogCommand(string Title, string Content, string Tags, int ReadingTime, string Summery, bool IsSuggested, List Files, Guid CategoryId) : IRequest; +public record UpdateBlogCommand(Guid Id, string Title, string Content, string Tags, int ReadingTime, string Summery, bool IsSuggested, List Files, Guid CategoryId) : IRequest; +public record DeleteBlogCommand(Guid Id) : IRequest; \ No newline at end of file diff --git a/HamyanEdalat.Domain/CommandQueries/Queries/BlogCategoryQueries.cs b/HamyanEdalat.Domain/CommandQueries/Queries/BlogCategoryQueries.cs new file mode 100644 index 0000000..237a6b0 --- /dev/null +++ b/HamyanEdalat.Domain/CommandQueries/Queries/BlogCategoryQueries.cs @@ -0,0 +1,4 @@ +namespace HamyanEdalat.Domain.CommandQueries.Queries; + +public record GetBlogCategoriesQuery(int Page) : IRequest>; +public record GetBlogCategoryQuery(Guid Id) : IRequest; \ No newline at end of file diff --git a/HamyanEdalat.Domain/CommandQueries/Queries/BlogQueries.cs b/HamyanEdalat.Domain/CommandQueries/Queries/BlogQueries.cs new file mode 100644 index 0000000..1f58fe6 --- /dev/null +++ b/HamyanEdalat.Domain/CommandQueries/Queries/BlogQueries.cs @@ -0,0 +1,6 @@ +using HamyanEdalat.Domain.Dtos.ResponseDtos; + +namespace HamyanEdalat.Domain.CommandQueries.Queries; + +public record GetBlogsQuery(int Page = 0) : IRequest; +public record GetBlogQuery(Guid Id) : IRequest; \ No newline at end of file diff --git a/HamyanEdalat.Domain/DomainConfig.cs b/HamyanEdalat.Domain/DomainConfig.cs new file mode 100644 index 0000000..2122328 --- /dev/null +++ b/HamyanEdalat.Domain/DomainConfig.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain +{ + public class DomainConfig + { + + } +} diff --git a/HamyanEdalat.Domain/Dtos/LargDto/BlogCategoryLDto.cs b/HamyanEdalat.Domain/Dtos/LargDto/BlogCategoryLDto.cs new file mode 100644 index 0000000..2cb8e97 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/LargDto/BlogCategoryLDto.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Domain.Dtos.LargDto; + +public class BlogCategoryLDto : BaseDto +{ + + public string Name { get; internal set; } = string.Empty; + public string Description { get; internal set; } = string.Empty; + public List Blogs { get; internal set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/LargDto/BlogLDto.cs b/HamyanEdalat.Domain/Dtos/LargDto/BlogLDto.cs new file mode 100644 index 0000000..160bb6a --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/LargDto/BlogLDto.cs @@ -0,0 +1,14 @@ +namespace HamyanEdalat.Domain.Dtos.LargDto; + +public class BlogLDto : BaseDto +{ + + public string Title { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public string Tags { get; set; } = string.Empty; + public int ReadingTime { get; set; } + public string Summery { get; set; } = string.Empty; + public bool IsSuggested { get; set; } + public Guid CategoryId { get; set; } + public List Files { get; set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/RequestDtos/LoginRequestDto.cs b/HamyanEdalat.Domain/Dtos/RequestDtos/LoginRequestDto.cs new file mode 100644 index 0000000..5c550f9 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/RequestDtos/LoginRequestDto.cs @@ -0,0 +1,8 @@ +namespace HamyanEdalat.Domain.Dtos.RequestDtos; + +public class LoginRequestDto +{ + public string UserName { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string VerifyCode { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs b/HamyanEdalat.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs new file mode 100644 index 0000000..a4454dc --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs @@ -0,0 +1,10 @@ +namespace HamyanEdalat.Domain.Dtos.RequestDtos; + +public class RoleActionRequestDto +{ + public Guid RoleId { get; set; } + public string Description { get; set; } = string.Empty; + public string EnglishName { get; set; } = string.Empty; + public string PersianName { get; set; } = string.Empty; + public List Permissions { get; set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/RequestDtos/SignUpRequestDto.cs b/HamyanEdalat.Domain/Dtos/RequestDtos/SignUpRequestDto.cs new file mode 100644 index 0000000..f665e7a --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/RequestDtos/SignUpRequestDto.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain.Dtos.RequestDtos; + +public class SignUpRequestDto +{ + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/RequestDtos/UserActionRequestDto.cs b/HamyanEdalat.Domain/Dtos/RequestDtos/UserActionRequestDto.cs new file mode 100644 index 0000000..9ef6727 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/RequestDtos/UserActionRequestDto.cs @@ -0,0 +1,14 @@ +namespace HamyanEdalat.Domain.Dtos.RequestDtos; + +public class UserActionRequestDto +{ + public Guid UserId { get; set; } + public string PhoneNumber { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public long BirthDateTimeStamp { get; set; } + public Gender Gender { get; set; } + public string NationalId { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public List RoleIds { get; set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/ResponseDtos/GetBlogsResponseDto.cs b/HamyanEdalat.Domain/Dtos/ResponseDtos/GetBlogsResponseDto.cs new file mode 100644 index 0000000..a018dc7 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/ResponseDtos/GetBlogsResponseDto.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain.Dtos.ResponseDtos; + +public class GetBlogsResponseDto +{ + public List Blogs { get; set; } = new(); + public PagerResponseDto Pager { get; set; } = new PagerResponseDto(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/ResponseDtos/PagerResponseDto.cs b/HamyanEdalat.Domain/Dtos/ResponseDtos/PagerResponseDto.cs new file mode 100644 index 0000000..35dcc85 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/ResponseDtos/PagerResponseDto.cs @@ -0,0 +1,8 @@ +namespace HamyanEdalat.Domain.Dtos.ResponseDtos; + +public class PagerResponseDto +{ + public int CurrentPage { get; set; } + public int TotalItems { get; set; } + public int TotalPage { get; set; } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs b/HamyanEdalat.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs new file mode 100644 index 0000000..ce1b937 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs @@ -0,0 +1,8 @@ +namespace HamyanEdalat.Domain.Dtos.ResponseDtos; + +public class ProfileResponseDto +{ + public List Roles { get; set; } = new(); + public ApplicationUserSDto? User { get; set; } + public List Permissions { get; set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs b/HamyanEdalat.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs new file mode 100644 index 0000000..366ae1c --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain.Dtos.ResponseDtos; + +public class VerifyCodeResponseDto +{ + public SignUpStatus SignUpStatus { get; set; } + +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/SmallDto/ApplicationUserSDto.cs b/HamyanEdalat.Domain/Dtos/SmallDto/ApplicationUserSDto.cs new file mode 100644 index 0000000..ec8d355 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/SmallDto/ApplicationUserSDto.cs @@ -0,0 +1,21 @@ +using HamyanEdalat.Domain.Entities.Users; + +namespace HamyanEdalat.Domain.Dtos.SmallDto; + +public class ApplicationUserSDto : BaseDto +{ + + public string PhoneNumber { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public DateTime BirthDate { get; set; } + public Gender Gender { get; set; } + public SignUpStatus SignUpStatus { get; set; } + public string NationalId { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string FullName => FirstName + " " + LastName; + public string RoleName { get; set; } = string.Empty; + + public List RoleIds { get; set; } = new(); + public long BirthDateTimeStamp => DateTimeExtensions.DateTimeToUnixTimeStamp(BirthDate); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/SmallDto/BlogCategorySDto.cs b/HamyanEdalat.Domain/Dtos/SmallDto/BlogCategorySDto.cs new file mode 100644 index 0000000..ff50235 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/SmallDto/BlogCategorySDto.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain.Dtos.SmallDto; + +public class BlogCategorySDto : BaseDto +{ + public string Name { get; internal set; } = string.Empty; + public string Description { get; internal set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/SmallDto/BlogSDto.cs b/HamyanEdalat.Domain/Dtos/SmallDto/BlogSDto.cs new file mode 100644 index 0000000..232b665 --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/SmallDto/BlogSDto.cs @@ -0,0 +1,13 @@ +namespace HamyanEdalat.Domain.Dtos.SmallDto; + +public class BlogSDto : BaseDto +{ + public string Title { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public string Tags { get; set; } = string.Empty; + public int ReadingTime { get; set; } + public string Summery { get; set; } = string.Empty; + public bool IsSuggested { get; set; } + public Guid CategoryId { get; set; } + public string CategoryName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Dtos/SmallDto/StorageFileSDto.cs b/HamyanEdalat.Domain/Dtos/SmallDto/StorageFileSDto.cs new file mode 100644 index 0000000..ccaca1d --- /dev/null +++ b/HamyanEdalat.Domain/Dtos/SmallDto/StorageFileSDto.cs @@ -0,0 +1,24 @@ +namespace HamyanEdalat.Domain.Dtos.SmallDto; + +public class StorageFileSDto : BaseDto +{ + public string Name { get; set; } = string.Empty; + public string FileLocation { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + public bool IsHeader { get; set; } + public bool IsPrimary { get; set; } + public StorageFileType FileType { get; set; } + public bool Selected { get; set; } + + public DateTime CreatedAt + { + get + { + string date = FileName.Split('.').First().Split('_').Last(); + if (!date.IsNullOrEmpty() && long.TryParse(date, out long longDate)) + return DateTimeExtensions.UnixTimeStampToDateTime(longDate); + return DateTime.MinValue; + } + } + +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Blogs/Blog.Aggregate.cs b/HamyanEdalat.Domain/Entities/Blogs/Blog.Aggregate.cs new file mode 100644 index 0000000..e3bdbb4 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Blogs/Blog.Aggregate.cs @@ -0,0 +1,32 @@ +namespace HamyanEdalat.Domain.Entities.Blogs; + +public partial class Blog +{ + public static Blog Create(string title, string content, string tags, int readingTime, string summery, bool isSuggested, Guid categoryId) + { + return new Blog(title, content, tags, readingTime, summery, isSuggested, categoryId); + } + + public BlogStorageFile AddFile(string name, string fileLocation, string fileName, bool isHeader, bool isPrimary, StorageFileType fileType) + { + var file = BlogStorageFile.Create(name, fileLocation, fileName, isHeader, isPrimary, fileType, this.Id); + Files.Add(file); + return file; + } +} + +public partial class BlogStorageFile +{ + public static BlogStorageFile Create(string name, string fileLocation, string fileName, bool isHeader, bool isPrimary, StorageFileType fileType, Guid blogId) + { + return new BlogStorageFile(name, fileLocation, fileName, isHeader, isPrimary, fileType, blogId); + } +} + +public partial class BlogCategory +{ + public static BlogCategory Create(string name,string description) + { + return new BlogCategory(name, description); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Blogs/Blog.cs b/HamyanEdalat.Domain/Entities/Blogs/Blog.cs new file mode 100644 index 0000000..473a19b --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Blogs/Blog.cs @@ -0,0 +1,34 @@ +namespace HamyanEdalat.Domain.Entities.Blogs; + +[AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)] +[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)] +[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] +[GenerateMapper] + +public partial class Blog : ApiEntity +{ + public Blog() + { + + } + public Blog(string title,string content,string tags, int readingTime,string summery, bool isSuggested, Guid categoryId) + { + Title = title; + Content = content; + Tags = tags; + ReadingTime = readingTime; + Summery = summery; + IsSuggested = isSuggested; + CategoryId = categoryId; + } + public string Title { get; internal set; } = string.Empty; + public string Content { get; internal set; } = string.Empty; + public string Tags { get; internal set; } = string.Empty; + public int ReadingTime { get; internal set; } + public string Summery { get; internal set; } = string.Empty; + public bool IsSuggested { get; internal set; } + public Guid CategoryId { get; internal set; } + public BlogCategory? Category { get; internal set; } + public List Files { get; internal set; } = new(); + +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Blogs/BlogCategory.cs b/HamyanEdalat.Domain/Entities/Blogs/BlogCategory.cs new file mode 100644 index 0000000..11fdb22 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Blogs/BlogCategory.cs @@ -0,0 +1,21 @@ +namespace HamyanEdalat.Domain.Entities.Blogs; +[AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)] +[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)] +[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] +[GenerateMapper] +public partial class BlogCategory : ApiEntity +{ + public BlogCategory() + { + + } + + public BlogCategory(string name, string description) + { + Name = name; + Description = description; + } + public string Name { get; internal set; } = string.Empty; + public string Description { get; internal set; } = string.Empty; + public List Blogs { get; internal set; } = new(); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Blogs/BlogStorageFile.cs b/HamyanEdalat.Domain/Entities/Blogs/BlogStorageFile.cs new file mode 100644 index 0000000..a36aea7 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Blogs/BlogStorageFile.cs @@ -0,0 +1,16 @@ +namespace HamyanEdalat.Domain.Entities.Blogs; + +public partial class BlogStorageFile : StorageFile +{ + public BlogStorageFile() + { + + } + public BlogStorageFile(string name, string fileLocation, string fileName, bool isHeader, bool isPrimary, StorageFileType fileType, Guid blogId) : + base(name, fileLocation, fileName, isHeader, isPrimary, fileType) + { + BlogId = blogId; + } + public Guid BlogId { get; internal set; } + public Blog? Blog { get; internal set; } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.Aggregate.cs b/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.Aggregate.cs new file mode 100644 index 0000000..8b65a65 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.Aggregate.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Domain.Entities.StorageFiles; + +public partial class StorageFile +{ + public static StorageFile Create(string name, string fileLocation, string fileName, bool isHeader, bool isPrimary, StorageFileType fileType) + { + return new StorageFile(name, fileLocation, fileName, isHeader, isPrimary, fileType); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.cs b/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.cs new file mode 100644 index 0000000..b460939 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/StorageFiles/StorageFile.cs @@ -0,0 +1,27 @@ +namespace HamyanEdalat.Domain.Entities.StorageFiles; +[AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)] +[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)] +[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] +[GenerateMapper] +public partial class StorageFile : ApiEntity +{ + public StorageFile() + { + + } + public StorageFile(string name, string fileLocation, string fileName, bool isHeader, bool isPrimary, StorageFileType fileType) + { + Name = name; + FileLocation = fileLocation; + FileName = fileName; + IsHeader = isHeader; + IsPrimary = isPrimary; + FileType = fileType; + } + public string Name { get; internal set; } = string.Empty; + public string FileLocation { get; internal set; } = string.Empty; + public string FileName { get; internal set; } = string.Empty; + public bool IsHeader { get; internal set; } + public bool IsPrimary { get; internal set; } + public StorageFileType FileType { get; internal set; } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Users/ApplicationRole.cs b/HamyanEdalat.Domain/Entities/Users/ApplicationRole.cs new file mode 100644 index 0000000..27a1e47 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Users/ApplicationRole.cs @@ -0,0 +1,8 @@ +namespace HamyanEdalat.Domain.Entities.Users; + +public class ApplicationRole : IdentityRole +{ + public string Description { get; set; } = string.Empty; + public string EnglishName { get; set; } = string.Empty; + public string PersianName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Users/ApplicationUser.cs b/HamyanEdalat.Domain/Entities/Users/ApplicationUser.cs new file mode 100644 index 0000000..2158e02 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Users/ApplicationUser.cs @@ -0,0 +1,19 @@ +namespace HamyanEdalat.Domain.Entities.Users; + +[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget )] +[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] + +[GenerateMapper] +public class ApplicationUser : IdentityUser +{ + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string NationalId { get; set; } = string.Empty; + + public string City { get; set; } = string.Empty; + public int CityId { get; set; } + + public DateTime BirthDate { get; set; } + public Gender Gender { get; set; } + public SignUpStatus SignUpStatus { get; set; } +} diff --git a/HamyanEdalat.Domain/Entities/Users/Gender.cs b/HamyanEdalat.Domain/Entities/Users/Gender.cs new file mode 100644 index 0000000..5414d3c --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Users/Gender.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Domain.Entities.Users; + +public enum Gender +{ + [Display(Name = "مرد")] + Male, + [Display(Name = "بانو")] + Female +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Entities/Users/SignUpStatus.cs b/HamyanEdalat.Domain/Entities/Users/SignUpStatus.cs new file mode 100644 index 0000000..05e2331 --- /dev/null +++ b/HamyanEdalat.Domain/Entities/Users/SignUpStatus.cs @@ -0,0 +1,8 @@ +namespace HamyanEdalat.Domain.Entities.Users; + +public enum SignUpStatus +{ + StartSignOn = 0, + PhoneNumberVerified = 1, + SignUpCompleted = 10, +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Enums/StorageFileType.cs b/HamyanEdalat.Domain/Enums/StorageFileType.cs new file mode 100644 index 0000000..4eedc2d --- /dev/null +++ b/HamyanEdalat.Domain/Enums/StorageFileType.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Domain.Enums; + +public enum StorageFileType +{ + [Display(Name = "Images")] + Image, + [Display(Name = "Videos")] + Video +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/FodyWeavers.xml b/HamyanEdalat.Domain/FodyWeavers.xml new file mode 100644 index 0000000..d5abfed --- /dev/null +++ b/HamyanEdalat.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/HamyanEdalat.Domain/HamyanEdalat.Domain.csproj b/HamyanEdalat.Domain/HamyanEdalat.Domain.csproj new file mode 100644 index 0000000..9f99ff0 --- /dev/null +++ b/HamyanEdalat.Domain/HamyanEdalat.Domain.csproj @@ -0,0 +1,72 @@ + + + + + + net5.0 + 10 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HamyanEdalat.Domain/Mappers/ApplicationUserMapper.g.cs b/HamyanEdalat.Domain/Mappers/ApplicationUserMapper.g.cs new file mode 100644 index 0000000..ff2e057 --- /dev/null +++ b/HamyanEdalat.Domain/Mappers/ApplicationUserMapper.g.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq.Expressions; +using HamyanEdalat.Domain.Dtos.SmallDto; +using HamyanEdalat.Domain.Entities.Users; + +namespace HamyanEdalat.Domain.Mappers +{ + public static partial class ApplicationUserMapper + { + public static ApplicationUser AdaptToApplicationUser(this ApplicationUserSDto p1) + { + return p1 == null ? null : new ApplicationUser() + { + FirstName = p1.FirstName, + LastName = p1.LastName, + NationalId = p1.NationalId, + BirthDate = p1.BirthDate, + Gender = p1.Gender, + SignUpStatus = p1.SignUpStatus, + Id = p1.Id, + Email = p1.Email, + PhoneNumber = p1.PhoneNumber + }; + } + public static ApplicationUser AdaptTo(this ApplicationUserSDto p2, ApplicationUser p3) + { + if (p2 == null) + { + return null; + } + ApplicationUser result = p3 ?? new ApplicationUser(); + + result.FirstName = p2.FirstName; + result.LastName = p2.LastName; + result.NationalId = p2.NationalId; + result.BirthDate = p2.BirthDate; + result.Gender = p2.Gender; + result.SignUpStatus = p2.SignUpStatus; + result.Id = p2.Id; + result.Email = p2.Email; + result.PhoneNumber = p2.PhoneNumber; + return result; + + } + public static ApplicationUserSDto AdaptToSDto(this ApplicationUser p4) + { + return p4 == null ? null : new ApplicationUserSDto() + { + PhoneNumber = p4.PhoneNumber, + FirstName = p4.FirstName, + LastName = p4.LastName, + BirthDate = p4.BirthDate, + Gender = p4.Gender, + SignUpStatus = p4.SignUpStatus, + NationalId = p4.NationalId, + Email = p4.Email, + Id = p4.Id + }; + } + public static ApplicationUserSDto AdaptTo(this ApplicationUser p5, ApplicationUserSDto p6) + { + if (p5 == null) + { + return null; + } + ApplicationUserSDto result = p6 ?? new ApplicationUserSDto(); + + result.PhoneNumber = p5.PhoneNumber; + result.FirstName = p5.FirstName; + result.LastName = p5.LastName; + result.BirthDate = p5.BirthDate; + result.Gender = p5.Gender; + result.SignUpStatus = p5.SignUpStatus; + result.NationalId = p5.NationalId; + result.Email = p5.Email; + result.Id = p5.Id; + return result; + + } + public static Expression> ProjectToSDto => p7 => new ApplicationUserSDto() + { + PhoneNumber = p7.PhoneNumber, + FirstName = p7.FirstName, + LastName = p7.LastName, + BirthDate = p7.BirthDate, + Gender = p7.Gender, + SignUpStatus = p7.SignUpStatus, + NationalId = p7.NationalId, + Email = p7.Email, + Id = p7.Id + }; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Mappers/BlogCategoryMapper.g.cs b/HamyanEdalat.Domain/Mappers/BlogCategoryMapper.g.cs new file mode 100644 index 0000000..cdb6756 --- /dev/null +++ b/HamyanEdalat.Domain/Mappers/BlogCategoryMapper.g.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using HamyanEdalat.Domain.Dtos.LargDto; +using HamyanEdalat.Domain.Dtos.SmallDto; +using HamyanEdalat.Domain.Entities.Blogs; + +namespace HamyanEdalat.Domain.Mappers +{ + public static partial class BlogCategoryMapper + { + public static BlogCategory AdaptToBlogCategory(this BlogCategoryLDto p1) + { + return p1 == null ? null : new BlogCategory() + { + Name = p1.Name, + Description = p1.Description, + Blogs = funcMain1(p1.Blogs), + Id = p1.Id, + CreatedAt = p1.CreatedAt + }; + } + public static BlogCategory AdaptTo(this BlogCategoryLDto p3, BlogCategory p4) + { + if (p3 == null) + { + return null; + } + BlogCategory result = p4 ?? new BlogCategory(); + + result.Name = p3.Name; + result.Description = p3.Description; + result.Blogs = funcMain2(p3.Blogs, result.Blogs); + result.Id = p3.Id; + result.CreatedAt = p3.CreatedAt; + return result; + + } + public static Expression> ProjectToBlogCategory => p7 => new BlogCategory() + { + Name = p7.Name, + Description = p7.Description, + Blogs = p7.Blogs.Select(p8 => new Blog() + { + Title = p8.Title, + Content = p8.Content, + Tags = p8.Tags, + ReadingTime = p8.ReadingTime, + Summery = p8.Summery, + IsSuggested = p8.IsSuggested, + CategoryId = p8.CategoryId, + Id = p8.Id, + CreatedAt = p8.CreatedAt + }).ToList(), + Id = p7.Id, + CreatedAt = p7.CreatedAt + }; + public static BlogCategoryLDto AdaptToLDto(this BlogCategory p9) + { + return p9 == null ? null : new BlogCategoryLDto() + { + Name = p9.Name, + Description = p9.Description, + Blogs = funcMain3(p9.Blogs), + Id = p9.Id, + CreatedAt = p9.CreatedAt + }; + } + public static BlogCategoryLDto AdaptTo(this BlogCategory p11, BlogCategoryLDto p12) + { + if (p11 == null) + { + return null; + } + BlogCategoryLDto result = p12 ?? new BlogCategoryLDto(); + + result.Name = p11.Name; + result.Description = p11.Description; + result.Blogs = funcMain4(p11.Blogs, result.Blogs); + result.Id = p11.Id; + result.CreatedAt = p11.CreatedAt; + return result; + + } + public static Expression> ProjectToLDto => p15 => new BlogCategoryLDto() + { + Name = p15.Name, + Description = p15.Description, + Blogs = p15.Blogs.Select(p16 => new BlogSDto() + { + Title = p16.Title, + Content = p16.Content, + Tags = p16.Tags, + ReadingTime = p16.ReadingTime, + Summery = p16.Summery, + IsSuggested = p16.IsSuggested, + CategoryId = p16.CategoryId, + CategoryName = p16.Category.Name, + Id = p16.Id, + CreatedAt = p16.CreatedAt + }).ToList(), + Id = p15.Id, + CreatedAt = p15.CreatedAt + }; + public static BlogCategory AdaptToBlogCategory(this BlogCategorySDto p17) + { + return p17 == null ? null : new BlogCategory() + { + Name = p17.Name, + Description = p17.Description, + Id = p17.Id, + CreatedAt = p17.CreatedAt + }; + } + public static BlogCategory AdaptTo(this BlogCategorySDto p18, BlogCategory p19) + { + if (p18 == null) + { + return null; + } + BlogCategory result = p19 ?? new BlogCategory(); + + result.Name = p18.Name; + result.Description = p18.Description; + result.Id = p18.Id; + result.CreatedAt = p18.CreatedAt; + return result; + + } + public static BlogCategorySDto AdaptToSDto(this BlogCategory p20) + { + return p20 == null ? null : new BlogCategorySDto() + { + Name = p20.Name, + Description = p20.Description, + Id = p20.Id, + CreatedAt = p20.CreatedAt + }; + } + public static BlogCategorySDto AdaptTo(this BlogCategory p21, BlogCategorySDto p22) + { + if (p21 == null) + { + return null; + } + BlogCategorySDto result = p22 ?? new BlogCategorySDto(); + + result.Name = p21.Name; + result.Description = p21.Description; + result.Id = p21.Id; + result.CreatedAt = p21.CreatedAt; + return result; + + } + public static Expression> ProjectToSDto => p23 => new BlogCategorySDto() + { + Name = p23.Name, + Description = p23.Description, + Id = p23.Id, + CreatedAt = p23.CreatedAt + }; + + private static List funcMain1(List p2) + { + if (p2 == null) + { + return null; + } + List result = new List(p2.Count); + + int i = 0; + int len = p2.Count; + + while (i < len) + { + BlogSDto item = p2[i]; + result.Add(item == null ? null : new Blog() + { + Title = item.Title, + Content = item.Content, + Tags = item.Tags, + ReadingTime = item.ReadingTime, + Summery = item.Summery, + IsSuggested = item.IsSuggested, + CategoryId = item.CategoryId, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + + private static List funcMain2(List p5, List p6) + { + if (p5 == null) + { + return null; + } + List result = new List(p5.Count); + + int i = 0; + int len = p5.Count; + + while (i < len) + { + BlogSDto item = p5[i]; + result.Add(item == null ? null : new Blog() + { + Title = item.Title, + Content = item.Content, + Tags = item.Tags, + ReadingTime = item.ReadingTime, + Summery = item.Summery, + IsSuggested = item.IsSuggested, + CategoryId = item.CategoryId, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + + private static List funcMain3(List p10) + { + if (p10 == null) + { + return null; + } + List result = new List(p10.Count); + + int i = 0; + int len = p10.Count; + + while (i < len) + { + Blog item = p10[i]; + result.Add(item == null ? null : new BlogSDto() + { + Title = item.Title, + Content = item.Content, + Tags = item.Tags, + ReadingTime = item.ReadingTime, + Summery = item.Summery, + IsSuggested = item.IsSuggested, + CategoryId = item.CategoryId, + CategoryName = item.Category == null ? null : item.Category.Name, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + + private static List funcMain4(List p13, List p14) + { + if (p13 == null) + { + return null; + } + List result = new List(p13.Count); + + int i = 0; + int len = p13.Count; + + while (i < len) + { + Blog item = p13[i]; + result.Add(item == null ? null : new BlogSDto() + { + Title = item.Title, + Content = item.Content, + Tags = item.Tags, + ReadingTime = item.ReadingTime, + Summery = item.Summery, + IsSuggested = item.IsSuggested, + CategoryId = item.CategoryId, + CategoryName = item.Category == null ? null : item.Category.Name, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Mappers/BlogMapper.g.cs b/HamyanEdalat.Domain/Mappers/BlogMapper.g.cs new file mode 100644 index 0000000..20fb9f1 --- /dev/null +++ b/HamyanEdalat.Domain/Mappers/BlogMapper.g.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using HamyanEdalat.Domain.Dtos.LargDto; +using HamyanEdalat.Domain.Dtos.SmallDto; +using HamyanEdalat.Domain.Entities.Blogs; + +namespace HamyanEdalat.Domain.Mappers +{ + public static partial class BlogMapper + { + public static Blog AdaptToBlog(this BlogLDto p1) + { + return p1 == null ? null : new Blog() + { + Title = p1.Title, + Content = p1.Content, + Tags = p1.Tags, + ReadingTime = p1.ReadingTime, + Summery = p1.Summery, + IsSuggested = p1.IsSuggested, + CategoryId = p1.CategoryId, + Files = funcMain1(p1.Files), + Id = p1.Id, + CreatedAt = p1.CreatedAt + }; + } + public static Blog AdaptTo(this BlogLDto p3, Blog p4) + { + if (p3 == null) + { + return null; + } + Blog result = p4 ?? new Blog(); + + result.Title = p3.Title; + result.Content = p3.Content; + result.Tags = p3.Tags; + result.ReadingTime = p3.ReadingTime; + result.Summery = p3.Summery; + result.IsSuggested = p3.IsSuggested; + result.CategoryId = p3.CategoryId; + result.Files = funcMain2(p3.Files, result.Files); + result.Id = p3.Id; + result.CreatedAt = p3.CreatedAt; + return result; + + } + public static Expression> ProjectToBlog => p7 => new Blog() + { + Title = p7.Title, + Content = p7.Content, + Tags = p7.Tags, + ReadingTime = p7.ReadingTime, + Summery = p7.Summery, + IsSuggested = p7.IsSuggested, + CategoryId = p7.CategoryId, + Files = p7.Files.Select(p8 => new BlogStorageFile() + { + Name = p8.Name, + FileLocation = p8.FileLocation, + FileName = p8.FileName, + IsHeader = p8.IsHeader, + IsPrimary = p8.IsPrimary, + FileType = p8.FileType, + Id = p8.Id, + CreatedAt = p8.CreatedAt + }).ToList(), + Id = p7.Id, + CreatedAt = p7.CreatedAt + }; + public static BlogLDto AdaptToLDto(this Blog p9) + { + return p9 == null ? null : new BlogLDto() + { + Title = p9.Title, + Content = p9.Content, + Tags = p9.Tags, + ReadingTime = p9.ReadingTime, + Summery = p9.Summery, + IsSuggested = p9.IsSuggested, + CategoryId = p9.CategoryId, + Files = funcMain3(p9.Files), + Id = p9.Id, + CreatedAt = p9.CreatedAt + }; + } + public static BlogLDto AdaptTo(this Blog p11, BlogLDto p12) + { + if (p11 == null) + { + return null; + } + BlogLDto result = p12 ?? new BlogLDto(); + + result.Title = p11.Title; + result.Content = p11.Content; + result.Tags = p11.Tags; + result.ReadingTime = p11.ReadingTime; + result.Summery = p11.Summery; + result.IsSuggested = p11.IsSuggested; + result.CategoryId = p11.CategoryId; + result.Files = funcMain4(p11.Files, result.Files); + result.Id = p11.Id; + result.CreatedAt = p11.CreatedAt; + return result; + + } + public static Expression> ProjectToLDto => p15 => new BlogLDto() + { + Title = p15.Title, + Content = p15.Content, + Tags = p15.Tags, + ReadingTime = p15.ReadingTime, + Summery = p15.Summery, + IsSuggested = p15.IsSuggested, + CategoryId = p15.CategoryId, + Files = p15.Files.Select(p16 => new StorageFileSDto() + { + Name = p16.Name, + FileLocation = p16.FileLocation, + FileName = p16.FileName, + IsHeader = p16.IsHeader, + IsPrimary = p16.IsPrimary, + FileType = p16.FileType, + Id = p16.Id + }).ToList(), + Id = p15.Id, + CreatedAt = p15.CreatedAt + }; + public static Blog AdaptToBlog(this BlogSDto p17) + { + return p17 == null ? null : new Blog() + { + Title = p17.Title, + Content = p17.Content, + Tags = p17.Tags, + ReadingTime = p17.ReadingTime, + Summery = p17.Summery, + IsSuggested = p17.IsSuggested, + CategoryId = p17.CategoryId, + Id = p17.Id, + CreatedAt = p17.CreatedAt + }; + } + public static Blog AdaptTo(this BlogSDto p18, Blog p19) + { + if (p18 == null) + { + return null; + } + Blog result = p19 ?? new Blog(); + + result.Title = p18.Title; + result.Content = p18.Content; + result.Tags = p18.Tags; + result.ReadingTime = p18.ReadingTime; + result.Summery = p18.Summery; + result.IsSuggested = p18.IsSuggested; + result.CategoryId = p18.CategoryId; + result.Id = p18.Id; + result.CreatedAt = p18.CreatedAt; + return result; + + } + public static BlogSDto AdaptToSDto(this Blog p20) + { + return p20 == null ? null : new BlogSDto() + { + Title = p20.Title, + Content = p20.Content, + Tags = p20.Tags, + ReadingTime = p20.ReadingTime, + Summery = p20.Summery, + IsSuggested = p20.IsSuggested, + CategoryId = p20.CategoryId, + CategoryName = p20.Category == null ? null : p20.Category.Name, + Id = p20.Id, + CreatedAt = p20.CreatedAt + }; + } + public static BlogSDto AdaptTo(this Blog p21, BlogSDto p22) + { + if (p21 == null) + { + return null; + } + BlogSDto result = p22 ?? new BlogSDto(); + + result.Title = p21.Title; + result.Content = p21.Content; + result.Tags = p21.Tags; + result.ReadingTime = p21.ReadingTime; + result.Summery = p21.Summery; + result.IsSuggested = p21.IsSuggested; + result.CategoryId = p21.CategoryId; + result.CategoryName = p21.Category == null ? null : p21.Category.Name; + result.Id = p21.Id; + result.CreatedAt = p21.CreatedAt; + return result; + + } + public static Expression> ProjectToSDto => p23 => new BlogSDto() + { + Title = p23.Title, + Content = p23.Content, + Tags = p23.Tags, + ReadingTime = p23.ReadingTime, + Summery = p23.Summery, + IsSuggested = p23.IsSuggested, + CategoryId = p23.CategoryId, + CategoryName = p23.Category.Name, + Id = p23.Id, + CreatedAt = p23.CreatedAt + }; + + private static List funcMain1(List p2) + { + if (p2 == null) + { + return null; + } + List result = new List(p2.Count); + + int i = 0; + int len = p2.Count; + + while (i < len) + { + StorageFileSDto item = p2[i]; + result.Add(item == null ? null : new BlogStorageFile() + { + Name = item.Name, + FileLocation = item.FileLocation, + FileName = item.FileName, + IsHeader = item.IsHeader, + IsPrimary = item.IsPrimary, + FileType = item.FileType, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + + private static List funcMain2(List p5, List p6) + { + if (p5 == null) + { + return null; + } + List result = new List(p5.Count); + + int i = 0; + int len = p5.Count; + + while (i < len) + { + StorageFileSDto item = p5[i]; + result.Add(item == null ? null : new BlogStorageFile() + { + Name = item.Name, + FileLocation = item.FileLocation, + FileName = item.FileName, + IsHeader = item.IsHeader, + IsPrimary = item.IsPrimary, + FileType = item.FileType, + Id = item.Id, + CreatedAt = item.CreatedAt + }); + i++; + } + return result; + + } + + private static List funcMain3(List p10) + { + if (p10 == null) + { + return null; + } + List result = new List(p10.Count); + + int i = 0; + int len = p10.Count; + + while (i < len) + { + BlogStorageFile item = p10[i]; + result.Add(item == null ? null : new StorageFileSDto() + { + Name = item.Name, + FileLocation = item.FileLocation, + FileName = item.FileName, + IsHeader = item.IsHeader, + IsPrimary = item.IsPrimary, + FileType = item.FileType, + Id = item.Id + }); + i++; + } + return result; + + } + + private static List funcMain4(List p13, List p14) + { + if (p13 == null) + { + return null; + } + List result = new List(p13.Count); + + int i = 0; + int len = p13.Count; + + while (i < len) + { + BlogStorageFile item = p13[i]; + result.Add(item == null ? null : new StorageFileSDto() + { + Name = item.Name, + FileLocation = item.FileLocation, + FileName = item.FileName, + IsHeader = item.IsHeader, + IsPrimary = item.IsPrimary, + FileType = item.FileType, + Id = item.Id + }); + i++; + } + return result; + + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Mappers/BlogStorageFileMapper.g.cs b/HamyanEdalat.Domain/Mappers/BlogStorageFileMapper.g.cs new file mode 100644 index 0000000..4e428bf --- /dev/null +++ b/HamyanEdalat.Domain/Mappers/BlogStorageFileMapper.g.cs @@ -0,0 +1,6 @@ +namespace HamyanEdalat.Domain.Mappers +{ + public static partial class BlogStorageFileMapper + { + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Mappers/StorageFileMapper.g.cs b/HamyanEdalat.Domain/Mappers/StorageFileMapper.g.cs new file mode 100644 index 0000000..e527f10 --- /dev/null +++ b/HamyanEdalat.Domain/Mappers/StorageFileMapper.g.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq.Expressions; +using HamyanEdalat.Domain.Dtos.SmallDto; +using HamyanEdalat.Domain.Entities.StorageFiles; + +namespace HamyanEdalat.Domain.Mappers +{ + public static partial class StorageFileMapper + { + public static StorageFile AdaptToStorageFile(this StorageFileSDto p1) + { + return p1 == null ? null : new StorageFile() + { + Name = p1.Name, + FileLocation = p1.FileLocation, + FileName = p1.FileName, + IsHeader = p1.IsHeader, + IsPrimary = p1.IsPrimary, + FileType = p1.FileType, + Id = p1.Id, + CreatedAt = p1.CreatedAt + }; + } + public static StorageFile AdaptTo(this StorageFileSDto p2, StorageFile p3) + { + if (p2 == null) + { + return null; + } + StorageFile result = p3 ?? new StorageFile(); + + result.Name = p2.Name; + result.FileLocation = p2.FileLocation; + result.FileName = p2.FileName; + result.IsHeader = p2.IsHeader; + result.IsPrimary = p2.IsPrimary; + result.FileType = p2.FileType; + result.Id = p2.Id; + result.CreatedAt = p2.CreatedAt; + return result; + + } + public static StorageFileSDto AdaptToSDto(this StorageFile p4) + { + return p4 == null ? null : new StorageFileSDto() + { + Name = p4.Name, + FileLocation = p4.FileLocation, + FileName = p4.FileName, + IsHeader = p4.IsHeader, + IsPrimary = p4.IsPrimary, + FileType = p4.FileType, + Id = p4.Id + }; + } + public static StorageFileSDto AdaptTo(this StorageFile p5, StorageFileSDto p6) + { + if (p5 == null) + { + return null; + } + StorageFileSDto result = p6 ?? new StorageFileSDto(); + + result.Name = p5.Name; + result.FileLocation = p5.FileLocation; + result.FileName = p5.FileName; + result.IsHeader = p5.IsHeader; + result.IsPrimary = p5.IsPrimary; + result.FileType = p5.FileType; + result.Id = p5.Id; + return result; + + } + public static Expression> ProjectToSDto => p7 => new StorageFileSDto() + { + Name = p7.Name, + FileLocation = p7.FileLocation, + FileName = p7.FileName, + IsHeader = p7.IsHeader, + IsPrimary = p7.IsPrimary, + FileType = p7.FileType, + Id = p7.Id + }; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Models/Claims/ApplicationClaims.cs b/HamyanEdalat.Domain/Models/Claims/ApplicationClaims.cs new file mode 100644 index 0000000..87b349e --- /dev/null +++ b/HamyanEdalat.Domain/Models/Claims/ApplicationClaims.cs @@ -0,0 +1,66 @@ +namespace HamyanEdalat.Domain.Models.Claims; + +public static class ApplicationClaims +{ + + public static ClaimDto ManageBlogs { get; } = new ClaimDto + { + Title = "مدیریت بلاگ ها", + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageBlogs, + }; + public static ClaimDto ViewBlogs { get; } = new ClaimDto + { + Title = "مشاهده بلاگ ها", + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewBlogs, + }; + + + + public static ClaimDto ManageBlogCategories { get; } = new ClaimDto + { + Title = "مدیریت دسته بندی ها", + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageBlogCategories, + }; + public static ClaimDto ViewBlogCategories { get; } = new ClaimDto + { + Title = "مشاهده دسته بندی ها", + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewBlogCategories, + }; + + + + public static List AllClaimDtos = new List + { + ManageBlogs, + ViewBlogs, + ManageBlogCategories, + ViewBlogCategories, + }; + + public static List AllClaims = new List + { + ManageBlogs.GetClaim, + ViewBlogs.GetClaim, + ManageBlogCategories.GetClaim, + ViewBlogCategories.GetClaim, + }; + + public static List ManagerClaims = new List + { + ManageBlogs.GetClaim, + ViewBlogs.GetClaim, + ManageBlogCategories.GetClaim, + ViewBlogCategories.GetClaim, + }; + + public static List CustomerClaims = new List + { + ViewBlogs.GetClaim, + ViewBlogCategories.GetClaim, + }; + +} diff --git a/HamyanEdalat.Domain/Models/Claims/ApplicationPermission.cs b/HamyanEdalat.Domain/Models/Claims/ApplicationPermission.cs new file mode 100644 index 0000000..a7aa55c --- /dev/null +++ b/HamyanEdalat.Domain/Models/Claims/ApplicationPermission.cs @@ -0,0 +1,10 @@ +namespace HamyanEdalat.Domain.Models.Claims; + +public static class ApplicationPermission +{ + public const string ManageBlogs = nameof(ManageBlogs); + public const string ViewBlogs = nameof(ManageBlogs); + + public const string ManageBlogCategories = nameof(ManageBlogCategories); + public const string ViewBlogCategories = nameof(ViewBlogCategories); +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Models/Claims/ClaimDto.cs b/HamyanEdalat.Domain/Models/Claims/ClaimDto.cs new file mode 100644 index 0000000..3d1f36e --- /dev/null +++ b/HamyanEdalat.Domain/Models/Claims/ClaimDto.cs @@ -0,0 +1,16 @@ +namespace HamyanEdalat.Domain.Models.Claims; +public class ClaimDto : INotifyPropertyChanged +{ + public string Value { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Detail { get; set; } = string.Empty; + public bool IsSelected { get; set; } = false; + + public event PropertyChangedEventHandler? PropertyChanged; + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + public Claim GetClaim => new Claim(Type, Value); +} diff --git a/HamyanEdalat.Domain/Models/Claims/CustomClaimType.cs b/HamyanEdalat.Domain/Models/Claims/CustomClaimType.cs new file mode 100644 index 0000000..bbd38e7 --- /dev/null +++ b/HamyanEdalat.Domain/Models/Claims/CustomClaimType.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Domain.Models.Claims +{ + public static class CustomClaimType + { + public static string Permission { get; } = "Permission"; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Domain/Models/Settings/SiteSettings.cs b/HamyanEdalat.Domain/Models/Settings/SiteSettings.cs new file mode 100644 index 0000000..6380f3d --- /dev/null +++ b/HamyanEdalat.Domain/Models/Settings/SiteSettings.cs @@ -0,0 +1,31 @@ +namespace HamyanEdalat.Domain.Models.Settings; + +public class SiteSettings +{ + public JwtSettings JwtSettings { get; set; } = new JwtSettings(); + public string BaseUrl { get; set; } = string.Empty; + public string AdminPanelBaseUrl { get; set; } = string.Empty; + public UserSetting RootUser { get; set; } = new UserSetting(); + public UserSetting ManagerUser { get; set; } = new UserSetting(); + public string KaveNegarApiKey { get; set; } = string.Empty; +} + + +public class JwtSettings +{ + public string SecretKey { get; set; } = string.Empty; + public string Issuer { get; set; } = string.Empty; + public string Audience { get; set; } = string.Empty; + public int ExpireAddDay { get; set; } +} + +public class UserSetting +{ + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string RoleName { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string Phone { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/HamyanEdalat.Infrastructure/HamyanEdalat.Infrastructure.csproj b/HamyanEdalat.Infrastructure/HamyanEdalat.Infrastructure.csproj new file mode 100644 index 0000000..d12d909 --- /dev/null +++ b/HamyanEdalat.Infrastructure/HamyanEdalat.Infrastructure.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HamyanEdalat.Infrastructure/InfrastructureConfig.cs b/HamyanEdalat.Infrastructure/InfrastructureConfig.cs new file mode 100644 index 0000000..be59dd9 --- /dev/null +++ b/HamyanEdalat.Infrastructure/InfrastructureConfig.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Infrastructure +{ + public class InfrastructureConfig + { + + } +} diff --git a/HamyanEdalat.Repository/Abstracts/ICurrentUserService.cs b/HamyanEdalat.Repository/Abstracts/ICurrentUserService.cs new file mode 100644 index 0000000..fe97737 --- /dev/null +++ b/HamyanEdalat.Repository/Abstracts/ICurrentUserService.cs @@ -0,0 +1,11 @@ +namespace HamyanEdalat.Repository.Abstracts; + +public interface ICurrentUserService : IScopedDependency +{ + string? UserId { get; } + string? RoleName { get; } + string? UserName { get; } + string? DeviceId { get; } + bool IsAuthorized { get; } + public List? Permissions { get; } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Behaviors/ValidationBehavior.cs b/HamyanEdalat.Repository/Behaviors/ValidationBehavior.cs new file mode 100644 index 0000000..78841ae --- /dev/null +++ b/HamyanEdalat.Repository/Behaviors/ValidationBehavior.cs @@ -0,0 +1,33 @@ +using ValidationException = HamyanEdalat.Common.Models.Exception.ValidationException; + +namespace HamyanEdalat.Repository.Behaviors; + +public class ValidationBehavior : IPipelineBehavior where TRequest : notnull +{ + private readonly IEnumerable> _validators; + + public ValidationBehavior(IEnumerable> validators) + { + _validators = validators; + } + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + var context = new ValidationContext(request); + List errors = new List(); + foreach (IValidator validator in _validators) + { + var result = await validator.ValidateAsync(context, cancellationToken); + if (!result.IsValid) + errors.AddRange(result.Errors.Select(v => new ValidationError(v.PropertyName, v.ErrorMessage)).Distinct()); + } + + if (errors.Any()) + { + throw new ValidationException(errors); + } + + + var response = await next(); + return response; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs b/HamyanEdalat.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs new file mode 100644 index 0000000..3a6c031 --- /dev/null +++ b/HamyanEdalat.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs @@ -0,0 +1,59 @@ +namespace HamyanEdalat.Repository.Extensions; + +public class DbContextOptionCustomExtensionsInfo : DbContextOptionsExtensionInfo +{ + public DbContextOptionCustomExtensionsInfo(IDbContextOptionsExtension extension) : base(extension) + { + } + + public override bool IsDatabaseProvider { get; } + public override string LogFragment { get; } = string.Empty; + + public override int GetServiceProviderHashCode() + { + return Extension.GetHashCode(); + } + + public override void PopulateDebugInfo(IDictionary debugInfo) + { + } + + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + return true; + } +} + +public class DbContextOptionCustomExtensions : IDbContextOptionsExtension +{ + public DbContextOptionCustomExtensions() + { + Info = new DbContextOptionCustomExtensionsInfo(this); + } + + public Assembly ProjectAssembly { get; set; } = Assembly.GetExecutingAssembly(); + + public void ApplyServices(IServiceCollection services) + { + } + + public void Validate(IDbContextOptions options) + { + } + + public DbContextOptionsExtensionInfo Info { get; } +} + +public static class ApplicationContextExtensions +{ + public static DbContextOptionsBuilder UseProjectAssembly(this DbContextOptionsBuilder contextOptions, + Assembly projectAssembly) + { + var extension = new DbContextOptionCustomExtensions + { + ProjectAssembly = projectAssembly + }; + ((IDbContextOptionsBuilderInfrastructure)contextOptions).AddOrUpdateExtension(extension); + return contextOptions; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Extensions/ModelBuilderExtensions.cs b/HamyanEdalat.Repository/Extensions/ModelBuilderExtensions.cs new file mode 100644 index 0000000..568f5bc --- /dev/null +++ b/HamyanEdalat.Repository/Extensions/ModelBuilderExtensions.cs @@ -0,0 +1,190 @@ +namespace HamyanEdalat.Repository.Extensions; + +public class ModelBuilderQueryFilter +{ + public void AddQueryFilterToModelBuilder(ModelBuilder modelBuilder, Type type) + { + var method = GetType().GetMethod("RegisterQueryFilter").MakeGenericMethod(type); + method.Invoke(this, new object[] { modelBuilder }); + } + + public void RegisterQueryFilter(ModelBuilder modelBuilder) where TQFilter : ApiEntity + { + var tt = typeof(TQFilter); + if (tt.BaseType == typeof(ApiEntity)) + modelBuilder.Entity().HasQueryFilter(e => e.IsRemoved == false); + } +} + +public static class ModelBuilderExtensions +{ + /// + /// Singularizin table name like Posts to Post or People to Person + /// + /// + public static void AddSingularizingTableNameConvention(this ModelBuilder modelBuilder) + { + var pluralizer = new Pluralizer(); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var tableName = entityType.GetTableName(); + entityType.SetTableName(pluralizer.Singularize(tableName)); + } + } + + + /// + /// Set NEWSEQUENTIALID() sql function for all columns named "Id" + /// + /// + /// Set to true if you want only "Identity" guid fields that named "Id" + public static void AddSequentialGuidForIdConvention(this ModelBuilder modelBuilder) + { + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var property = entityType.GetProperties() + .Where(p => p.Name.Contains("Id", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + foreach (var mutableProperty in property) + modelBuilder.AddDefaultValueSqlConvention(mutableProperty?.Name, typeof(Guid), "gen_random_uuid()"); + } + } + + /// + /// Set DefaultValueSql for sepecific property name and type + /// + /// + /// Name of property wants to set DefaultValueSql for + /// Type of property wants to set DefaultValueSql for + /// DefaultValueSql like "NEWSEQUENTIALID()" + public static void AddDefaultValueSqlConvention(this ModelBuilder modelBuilder, string propertyName, + Type propertyType, string defaultValueSql) + { + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var property = entityType.GetProperties() + .SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); + if (property != null && property.ClrType == propertyType) + property.SetDefaultValueSql(defaultValueSql); + } + } + + /// + /// Set DeleteBehavior.Restrict by default for relations + /// + /// + public static void AddRestrictDeleteBehaviorConvention(this ModelBuilder modelBuilder) + { + var cascadeFKs = modelBuilder.Model.GetEntityTypes() + .SelectMany(t => t.GetForeignKeys()) + .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade); + foreach (var fk in cascadeFKs) + { + fk.DeleteBehavior = DeleteBehavior.Restrict; + fk.IsRequired = false; + } + } + + /// + /// Dynamicaly load all IEntityTypeConfiguration with Reflection + /// + /// + /// Assemblies contains Entities + public static void RegisterEntityTypeConfiguration(this ModelBuilder modelBuilder, params Assembly[] assemblies) + { + var applyGenericMethod = typeof(ModelBuilder) + .GetMethods() + .First(m => m.Name == nameof(ModelBuilder.ApplyConfiguration)); + + var types = assemblies.SelectMany(a => a.GetExportedTypes()) + .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic); + + foreach (var type in types) + foreach (var iface in type.GetInterfaces()) + if (iface.IsConstructedGenericType && + iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) + { + var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]); + applyConcreteMethod.Invoke(modelBuilder, new[] { Activator.CreateInstance(type) }); + } + } + + + /// + /// Pluralizing table name like Post to Posts or Person to People + /// + /// + public static void AddPluralizingTableNameConvention(this ModelBuilder modelBuilder) + { + var pluralizer = new Pluralizer(); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + if (entityType.BaseType == null) + { + var tableName = entityType.GetTableName(); + entityType.SetTableName(pluralizer.Pluralize(tableName)); + } + } + + /// + /// Dynamicaly register all Entities that inherit from specific BaseType + /// + /// + /// Base type that Entities inherit from this + /// Assemblies contains Entities + public static void RegisterAllEntities(this ModelBuilder modelBuilder, + ILogger _logger, params Assembly[] assemblies) where BaseType : ApiEntity + { + var types = assemblies.SelectMany(a => a.GetExportedTypes()) + .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic && typeof(BaseType) + .IsAssignableFrom(c)); + var builderQueryFilter = new ModelBuilderQueryFilter(); + foreach (var type in types) + { + modelBuilder.Entity(type); + builderQueryFilter.AddQueryFilterToModelBuilder(modelBuilder, type); + } + } + + /// + /// Dynamicaly register all Entities that inherit from specific BaseType + /// + /// + /// Base type that Entities inherit from this + /// Assemblies contains Entities + public static void RegisterAllEntitiesV02(this ModelBuilder builder, ILogger _logger, params Assembly[] assemblies) where BaseType : ApiEntity + { + var types = assemblies.SelectMany(a => a.GetExportedTypes()) + .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic && typeof(BaseType) + .IsAssignableFrom(c)); + var builderQueryFilter = new ModelBuilderQueryFilter(); + + foreach (var type in types) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + // On Model Creating + var onModelCreatingMethod = + type.GetMethods().FirstOrDefault(x => x.Name == "OnModelCreating"); + + if (onModelCreatingMethod != null) + onModelCreatingMethod.Invoke(type, new object[] { builder }); + else + { + // On Base Model Creating + if (type.BaseType == null || type.BaseType != typeof(BaseType)) continue; + + var baseOnModelCreatingMethod = type.BaseType.GetMethods() + .FirstOrDefault(x => x.Name == "OnModelCreating"); + + if (baseOnModelCreatingMethod == null) + continue; + + baseOnModelCreatingMethod.Invoke(typeof(BaseType), new object[] { builder }); + } + + builderQueryFilter.AddQueryFilterToModelBuilder(builder, type); + stopwatch.Stop(); + _logger.LogInformation($"MODEL BUILDER {type.Name} In : {stopwatch.ElapsedMilliseconds}ms"); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/HamyanEdalat.Repository.csproj b/HamyanEdalat.Repository/HamyanEdalat.Repository.csproj new file mode 100644 index 0000000..f4b5447 --- /dev/null +++ b/HamyanEdalat.Repository/HamyanEdalat.Repository.csproj @@ -0,0 +1,73 @@ + + + + net8.0 + enable + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/CreateBlogCategoryCommandHandler.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/CreateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..0ed2b85 --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/CreateBlogCategoryCommandHandler.cs @@ -0,0 +1,19 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories; + +public class CreateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public CreateBlogCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(CreateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var ent = BlogCategory.Create(request.Name, request.Description); + _repositoryWrapper.SetRepository() + .Add(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/DeleteBlogCategoryCommandHandler.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/DeleteBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..552310e --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/DeleteBlogCategoryCommandHandler.cs @@ -0,0 +1,24 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories; + +public class DeleteBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public DeleteBlogCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(DeleteBlogCategoryCommand request, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(bc => bc.Id == request.Id, cancellationToken); + if (ent == null) + throw new AppException("Blog Category not found", ApiResultStatusCode.NotFound); + + _repositoryWrapper.SetRepository() + .Delete(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoriesQueryHandler.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoriesQueryHandler.cs new file mode 100644 index 0000000..c53ba4c --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoriesQueryHandler.cs @@ -0,0 +1,21 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories; + +public class GetBlogCategoriesQueryHandler : IRequestHandler> +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public GetBlogCategoriesQueryHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task> Handle(GetBlogCategoriesQuery request, CancellationToken cancellationToken) + { + return await _repositoryWrapper.SetRepository() + .TableNoTracking + .Skip(request.Page * 20) + .Take(20) + .Select(BlogCategoryMapper.ProjectToSDto) + .ToListAsync(cancellationToken); + + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoryCommandHandler.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..a1697cf --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/GetBlogCategoryCommandHandler.cs @@ -0,0 +1,23 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories; + +public class GetBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public GetBlogCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(GetBlogCategoryQuery request, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository() + .TableNoTracking + .Where(bc => bc.Id == request.Id) + .Select(BlogCategoryMapper.ProjectToLDto) + .FirstOrDefaultAsync(cancellationToken); + if (ent == null) + throw new AppException("BlogCategory not found", ApiResultStatusCode.NotFound); + + return ent; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/UpdateBlogCategoryCommandHandler.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/UpdateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..66bbf55 --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/UpdateBlogCategoryCommandHandler.cs @@ -0,0 +1,29 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories; + +public class UpdateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public UpdateBlogCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(UpdateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(bc => bc.Id == request.Id, cancellationToken); + if (ent == null) + throw new AppException("Blog Category not found", ApiResultStatusCode.NotFound); + + var newEnt = BlogCategory.Create(request.Name, request.Description); + newEnt.CreatedAt = ent.CreatedAt; + newEnt.CreatedBy = ent.CreatedBy; + newEnt.Id = ent.Id; + + _repositoryWrapper.SetRepository() + .Update(newEnt); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/BlogCategories/Validators/CreateBlogCategoryCommandValidator.cs b/HamyanEdalat.Repository/Handlers/BlogCategories/Validators/CreateBlogCategoryCommandValidator.cs new file mode 100644 index 0000000..7a2f33e --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/BlogCategories/Validators/CreateBlogCategoryCommandValidator.cs @@ -0,0 +1,12 @@ +namespace HamyanEdalat.Repository.Handlers.BlogCategories.Validators; + +public class CreateBlogCategoryCommandValidator : AbstractValidator +{ + public CreateBlogCategoryCommandValidator() + { + RuleFor(cb => cb.Name) + .NotNull() + .NotEmpty() + .WithMessage("نام دسته بندی را وارد کنید"); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/CreateBlogCommandHandler.cs b/HamyanEdalat.Repository/Handlers/Blogs/CreateBlogCommandHandler.cs new file mode 100644 index 0000000..775472a --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/CreateBlogCommandHandler.cs @@ -0,0 +1,26 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs; + +public class CreateBlogCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public CreateBlogCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(CreateBlogCommand request, CancellationToken cancellationToken) + { + var ent = Blog.Create(request.Title, request.Content, request.Tags, request.ReadingTime, request.Summery, + request.IsSuggested, request.CategoryId); + + foreach (var file in request.Files) + { + ent.AddFile(file.Name, file.FileLocation, file.FileName, file.IsHeader, file.IsPrimary, file.FileType); + } + + _repositoryWrapper.SetRepository() + .Add(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/DeleteBlogCommandHandler.cs b/HamyanEdalat.Repository/Handlers/Blogs/DeleteBlogCommandHandler.cs new file mode 100644 index 0000000..0a8ec4f --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/DeleteBlogCommandHandler.cs @@ -0,0 +1,26 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs; + +public class DeleteBlogCommandHandler :IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public DeleteBlogCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(DeleteBlogCommand request, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(b => b.Id == request.Id, cancellationToken); + + if (ent == null) + throw new AppException("Blog is not found", ApiResultStatusCode.NotFound); + + _repositoryWrapper.SetRepository() + .Delete(ent); + + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/GetBlogQueryHandler.cs b/HamyanEdalat.Repository/Handlers/Blogs/GetBlogQueryHandler.cs new file mode 100644 index 0000000..24aaa22 --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/GetBlogQueryHandler.cs @@ -0,0 +1,24 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs; + +public class GetBlogQueryHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public GetBlogQueryHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(GetBlogQuery request, CancellationToken cancellationToken) + { + var blog = await _repositoryWrapper.SetRepository() + .TableNoTracking + .Where(b => b.Id == request.Id) + .Select(BlogMapper.ProjectToLDto) + .FirstOrDefaultAsync(cancellationToken); + + if (blog == null) + throw new AppException("Blog not found", ApiResultStatusCode.NotFound); + + return blog; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/GetBlogsQueryHandler.cs b/HamyanEdalat.Repository/Handlers/Blogs/GetBlogsQueryHandler.cs new file mode 100644 index 0000000..d15ffd2 --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/GetBlogsQueryHandler.cs @@ -0,0 +1,29 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs; + +public class GetBlogsQueryHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public GetBlogsQueryHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(GetBlogsQuery request, CancellationToken cancellationToken) + { + var originals = _repositoryWrapper.SetRepository() + .TableNoTracking; + + var blogs = await originals.Skip(request.Page * 20) + .Take(20) + .Select(BlogMapper.ProjectToSDto) + .ToListAsync(cancellationToken); + + var response = new GetBlogsResponseDto(); + response.Blogs = blogs; + response.Pager.CurrentPage = request.Page; + response.Pager.TotalItems = await originals.CountAsync(cancellationToken); + response.Pager.TotalPage = response.Pager.TotalItems / 20; + + return response; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/UpdateBlogCommandHandler.cs b/HamyanEdalat.Repository/Handlers/Blogs/UpdateBlogCommandHandler.cs new file mode 100644 index 0000000..33e8c17 --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/UpdateBlogCommandHandler.cs @@ -0,0 +1,32 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs; + +public class UpdateBlogCommandHandler : IRequestHandler +{ + private readonly IRepositoryWrapper _repositoryWrapper; + + public UpdateBlogCommandHandler(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + public async Task Handle(UpdateBlogCommand request, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(b => b.Id == request.Id, cancellationToken); + + if (ent == null) + throw new AppException("Blog not found", ApiResultStatusCode.NotFound); + + var newEnt = Blog.Create(request.Title, request.Content, request.Tags, request.ReadingTime, request.Summery, + request.IsSuggested, request.CategoryId); + + newEnt.CreatedAt = ent.CreatedAt; + newEnt.CreatedBy = ent.CreatedBy; + newEnt.Id = ent.Id; + + _repositoryWrapper.SetRepository() + .Update(newEnt); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Handlers/Blogs/Validators/CreateBlogCommandValidator.cs b/HamyanEdalat.Repository/Handlers/Blogs/Validators/CreateBlogCommandValidator.cs new file mode 100644 index 0000000..fc8ecfe --- /dev/null +++ b/HamyanEdalat.Repository/Handlers/Blogs/Validators/CreateBlogCommandValidator.cs @@ -0,0 +1,27 @@ +namespace HamyanEdalat.Repository.Handlers.Blogs.Validators; + +public class CreateBlogCommandValidator : AbstractValidator +{ + public CreateBlogCommandValidator() + { + RuleFor(b => b.Content) + .NotNull() + .NotEmpty() + .WithMessage("محتوا بلاگ نمی تواند خالی باشد"); + + RuleFor(b => b.Title) + .NotNull() + .NotEmpty() + .WithMessage("عنوان بلاگ را وارد کنید"); + + RuleFor(b => b.Tags) + .NotNull() + .NotEmpty() + .WithMessage("تگ های بلاگ را وارد کنید"); + + RuleFor(b => b.Summery) + .NotNull() + .NotEmpty() + .WithMessage("خلاصه بلاگ را واردی کنید"); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Models/ApplicationContext.cs b/HamyanEdalat.Repository/Models/ApplicationContext.cs new file mode 100644 index 0000000..f8d987e --- /dev/null +++ b/HamyanEdalat.Repository/Models/ApplicationContext.cs @@ -0,0 +1,49 @@ +using HamyanEdalat.Repository.Extensions; + +namespace HamyanEdalat.Repository.Models; + + +public class ApplicationContext : IdentityDbContext +{ + private readonly ILogger _logger; + private readonly Assembly _projectAssembly; + + public ApplicationContext(DbContextOptions options, ILogger logger) : base(options) + { + _logger = logger; + _projectAssembly = options.GetExtension().ProjectAssembly; + } + + + protected override void OnModelCreating(ModelBuilder builder) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + base.OnModelCreating(builder); + var entitiesAssembly = _projectAssembly; + builder.RegisterAllEntities(_logger, entitiesAssembly); + stopwatch.Stop(); + _logger.LogInformation($"!!!!!!! RegisterAllEntities : {stopwatch.ElapsedMilliseconds}ms !!!!!!!"); + + + RenameIdentityTables(builder); + builder.RegisterEntityTypeConfiguration(entitiesAssembly); + builder.AddPluralizingTableNameConvention(); + builder.AddRestrictDeleteBehaviorConvention(); + //builder.AddSequentialGuidForIdConvention(); + } + + protected void RenameIdentityTables(ModelBuilder builder) + { + builder.HasDefaultSchema("public"); + + builder.Entity().ToTable("Users"); + builder.Entity().ToTable("Roles"); + builder.Entity>().ToTable("RoleClaims"); + builder.Entity>().ToTable("UserRoles"); + builder.Entity>().ToTable("Claims"); + builder.Entity>().ToTable("Logins"); + builder.Entity>().ToTable("Tokens"); + + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/BaseRepository.cs b/HamyanEdalat.Repository/Repositories/Base/BaseRepository.cs new file mode 100644 index 0000000..c539523 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/BaseRepository.cs @@ -0,0 +1,115 @@ +namespace HamyanEdalat.Repository.Repositories.Base +{ + public class BaseRepository : Repository, IBaseRepository where T : class, IApiEntity + { + private readonly ICurrentUserService _currentUserService; + + public BaseRepository(ApplicationContext dbContext, ICurrentUserService currentUserService) : base(dbContext) + { + _currentUserService = currentUserService; + } + + public virtual async ValueTask GetByIdAsync(CancellationToken cancellationToken, params object[] ids) + { + var founded = await Entities.FindAsync(ids, cancellationToken); + if (founded == null) + throw new AppException($"{nameof(T)} Not Found", ApiResultStatusCode.NotFound); + return founded; + } + + #region Sync Methods + + public virtual T GetById(params object[] ids) + { + var ent = Entities.Find(ids); + if (ent == null) + throw new AppException($"{nameof(T)} Not Found", ApiResultStatusCode.NotFound); + Detach(ent); + return ent; + } + + public virtual void Add(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Entities.Add(entity); + } + + public virtual void AddRange(IEnumerable entities) + { + var apiEntities = entities as T[] ?? entities.ToArray(); + AssertExtensions.NotNull(apiEntities, nameof(entities)); + Entities.AddRange(apiEntities); + } + + public virtual void Update(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Detach(entity); + Entities.Update(entity); + } + + public virtual void UpdateRange(IEnumerable entities) + { + var apiEntities = entities as T[] ?? entities.ToArray(); + AssertExtensions.NotNull(apiEntities, nameof(entities)); + Entities.UpdateRange(apiEntities); + } + + public void HardDelete(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Entities.Remove(entity); + } + + public virtual void Delete(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + + Entities.Entry(entity).Property(e => e.RemovedAt) + .CurrentValue = DateTime.Now; + Entities.Entry(entity).Property(e => e.IsRemoved) + .CurrentValue = true; + if (_currentUserService.UserName != null) + Entities.Entry(entity).Property(e => e.RemovedBy) + .CurrentValue = _currentUserService.UserName; + Entities.Update(entity); + } + + public virtual void DeleteRange(IEnumerable entities) + { + var apiEntities = entities.ToList(); + AssertExtensions.NotNull(apiEntities, nameof(entities)); + foreach (var entity in apiEntities) + { + Entities.Entry(entity).Property(e => e.RemovedAt) + .CurrentValue = DateTime.Now; + Entities.Entry(entity).Property(e => e.IsRemoved) + .CurrentValue = true; + if (_currentUserService.UserName != null) + Entities.Entry(entity).Property(e => e.RemovedBy) + .CurrentValue = _currentUserService.UserName; + Entities.Update(entity); + } + } + + #endregion + + #region Attach & Detach + + public virtual void Detach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + var entry = DbContext.Entry(entity); + entry.State = EntityState.Detached; + } + + public virtual void Attach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + if (DbContext.Entry(entity).State == EntityState.Detached) + Entities.Attach(entity); + } + + #endregion + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Contracts/IBaseRepository.cs b/HamyanEdalat.Repository/Repositories/Base/Contracts/IBaseRepository.cs new file mode 100644 index 0000000..8506275 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Contracts/IBaseRepository.cs @@ -0,0 +1,6 @@ +namespace HamyanEdalat.Repository.Repositories.Base.Contracts +{ + public interface IBaseRepository : IDisposable, IReadRepository, IWriteRepository where T : class, IApiEntity + { + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Contracts/IReadRepository.cs b/HamyanEdalat.Repository/Repositories/Base/Contracts/IReadRepository.cs new file mode 100644 index 0000000..3167e91 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Contracts/IReadRepository.cs @@ -0,0 +1,13 @@ +namespace HamyanEdalat.Repository.Repositories.Base.Contracts +{ + public interface IReadRepository where T : class, IApiEntity + { + DbSet Entities { get; } + IQueryable ExecuteCommand(FormattableString command); + IQueryable Table { get; } + IQueryable TableNoTracking { get; } + T GetById(params object[] ids); + ValueTask GetByIdAsync(CancellationToken cancellationToken, params object[] ids); + + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepository.cs b/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepository.cs new file mode 100644 index 0000000..f011873 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepository.cs @@ -0,0 +1,10 @@ +namespace HamyanEdalat.Repository.Repositories.Base.Contracts +{ + internal interface IRepository where T : class, IApiEntity + { + DbSet Entities { get; } + IQueryable Table { get; } + IQueryable TableNoTracking { get; } + + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs b/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs new file mode 100644 index 0000000..db0df96 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs @@ -0,0 +1,11 @@ +namespace HamyanEdalat.Repository.Repositories.Base.Contracts +{ + public interface IRepositoryWrapper : IDisposable , IScopedDependency + { + IBaseRepository SetRepository() where T : ApiEntity; + Task BeginTransaction(CancellationToken cancellationToken); + Task RollBackAsync(CancellationToken cancellationToken); + Task CommitAsync(CancellationToken cancellationToken); + Task SaveChangesAsync(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Contracts/IWriteRepository.cs b/HamyanEdalat.Repository/Repositories/Base/Contracts/IWriteRepository.cs new file mode 100644 index 0000000..16ffd58 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Contracts/IWriteRepository.cs @@ -0,0 +1,15 @@ +namespace HamyanEdalat.Repository.Repositories.Base.Contracts +{ + public interface IWriteRepository where T : class, IApiEntity + { + void Add(T entity); + void AddRange(IEnumerable entities); + void Delete(T entity); + void HardDelete(T entity); + void DeleteRange(IEnumerable entities); + void Update(T entity); + void UpdateRange(IEnumerable entities); + void Detach(T entity); + void Attach(T entity); + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/ReadRepository.cs b/HamyanEdalat.Repository/Repositories/Base/ReadRepository.cs new file mode 100644 index 0000000..03d60e1 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/ReadRepository.cs @@ -0,0 +1,44 @@ +namespace HamyanEdalat.Repository.Repositories.Base +{ + public class ReadRepository : Repository, IDisposable, IReadRepository where T : class, IApiEntity + { + public ReadRepository( + ApplicationContext dbContext) : base(dbContext) + { + } + + public new void Dispose() + { + DbContext?.Dispose(); + } + + + public virtual T GetById(params object[] ids) + { + var ent = Entities.Find(ids); + if (ent == null) + throw new AppException("Ent not found"); + Detach(ent); + return ent; + } + + public virtual ValueTask GetByIdAsync(CancellationToken cancellationToken, params object[] ids) + { + return Entities.FindAsync(ids, cancellationToken); + } + + public virtual void Detach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + var entry = DbContext.Entry(entity); + entry.State = EntityState.Detached; + } + + public virtual void Attach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + if (DbContext.Entry(entity).State == EntityState.Detached) + Entities.Attach(entity); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/Repository.cs b/HamyanEdalat.Repository/Repositories/Base/Repository.cs new file mode 100644 index 0000000..bf680d5 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/Repository.cs @@ -0,0 +1,36 @@ +namespace HamyanEdalat.Repository.Repositories.Base +{ + public class Repository : IRepository where T : class, IApiEntity + { + protected readonly ApplicationContext DbContext; + + public Repository(ApplicationContext dbContext) + { + DbContext = dbContext; + Entities = DbContext.Set(); + DbContext.ChangeTracker.Clear(); + } + + public DbSet Entities { get; } + + public virtual IQueryable Table => Entities.Where(e => !e.IsRemoved); + + public virtual IQueryable TableNoTracking => Table.AsNoTracking(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + DbContext?.Dispose(); + } + + public IQueryable ExecuteCommand(FormattableString command) + { + return DbContext.Set().FromSql(command); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/Base/RepositoryWrapper.cs b/HamyanEdalat.Repository/Repositories/Base/RepositoryWrapper.cs new file mode 100644 index 0000000..14f72a6 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/RepositoryWrapper.cs @@ -0,0 +1,64 @@ +namespace HamyanEdalat.Repository.Repositories.Base; +public class RepositoryWrapper : IRepositoryWrapper +{ + private readonly ApplicationContext _context; + private readonly ICurrentUserService _currentUserService; + private IDbContextTransaction? _currentTransaction; + public RepositoryWrapper(ApplicationContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public IBaseRepository SetRepository() where T : ApiEntity => new BaseRepository(_context, _currentUserService); + + + public async Task RollBackAsync(CancellationToken cancellationToken) + { + if (_currentTransaction == null) + throw new ArgumentNullException(nameof(_currentTransaction)); + await _currentTransaction.RollbackAsync(cancellationToken); + } + public async Task CommitAsync(CancellationToken cancellationToken) + { + if (_currentTransaction == null) + throw new ArgumentNullException(nameof(_currentTransaction)); + await _currentTransaction.CommitAsync(cancellationToken); + } + public async Task BeginTransaction(CancellationToken cancellationToken) + { + _currentTransaction = await _context.Database.BeginTransactionAsync(cancellationToken); + } + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + SetAuditables(); + await _context.SaveChangesAsync(cancellationToken); + } + + private void SetAuditables() + { + IEnumerable> entries = _context.ChangeTracker.Entries(); + foreach (EntityEntry entity in entries) + { + if (entity.State == EntityState.Added) + { + entity.Property(e => e.CreatedAt) + .CurrentValue = DateTime.Now; + } + + if (entity.State == EntityState.Modified) + { + entity.Property(e => e.ModifiedAt) + .CurrentValue = DateTime.Now; + if (_currentUserService.UserName != null) + entity.Property(e => e.ModifiedBy) + .CurrentValue = _currentUserService.UserName; + } + } + } + public void Dispose() + { + _currentTransaction?.Dispose(); + _context?.Dispose(); + } +} diff --git a/HamyanEdalat.Repository/Repositories/Base/WriteRepository.cs b/HamyanEdalat.Repository/Repositories/Base/WriteRepository.cs new file mode 100644 index 0000000..e456b36 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/Base/WriteRepository.cs @@ -0,0 +1,94 @@ +namespace HamyanEdalat.Repository.Repositories.Base +{ + public class WriteRepository : Repository, IDisposable, IWriteRepository where T : class, IApiEntity + { + private readonly ICurrentUserService _currentUserService; + + public WriteRepository(ApplicationContext dbContext,ICurrentUserService currentUserService) : base(dbContext) + { + _currentUserService = currentUserService; + } + + public void Dispose() + { + DbContext?.Dispose(); + } + + public void HardDelete(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Entities.Remove(entity); + } + + public virtual void Add(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Entities.Add(entity); + } + + public virtual void AddRange(IEnumerable entities) + { + AssertExtensions.NotNull(entities, nameof(entities)); + Entities.AddRange(entities); + } + + public virtual void Update(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + Detach(entity); + Entities.Update(entity); + } + + public virtual void UpdateRange(IEnumerable entities) + { + AssertExtensions.NotNull(entities, nameof(entities)); + Entities.UpdateRange(entities); + } + + public virtual void Delete(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + + Entities.Entry(entity).Property(e => e.RemovedAt) + .CurrentValue = DateTime.Now; + Entities.Entry(entity).Property(e => e.IsRemoved) + .CurrentValue = true; + if (_currentUserService.UserName != null) + Entities.Entry(entity).Property(e => e.RemovedBy) + .CurrentValue = _currentUserService.UserName; + Entities.Update(entity); + } + + public virtual void DeleteRange(IEnumerable entities) + { + var apiEntities = entities.ToList(); + AssertExtensions.NotNull(apiEntities, nameof(entities)); + foreach (var entity in apiEntities) + { + Entities.Entry(entity).Property(e => e.RemovedAt) + .CurrentValue = DateTime.Now; + Entities.Entry(entity).Property(e => e.IsRemoved) + .CurrentValue = true; + if (_currentUserService.UserName != null) + Entities.Entry(entity).Property(e => e.RemovedBy) + .CurrentValue = _currentUserService.UserName; + Entities.Update(entity); + } + } + + public virtual void Detach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + var entry = DbContext.Entry(entity); + if (entry != null) + entry.State = EntityState.Detached; + } + + public virtual void Attach(T entity) + { + AssertExtensions.NotNull(entity, nameof(entity)); + if (DbContext.Entry(entity).State == EntityState.Detached) + Entities.Attach(entity); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/UnitOfWork/IUnitOfWork.cs b/HamyanEdalat.Repository/Repositories/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 0000000..ff5a3f0 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Repository.Repositories.UnitOfWork; + +public interface IUnitOfWork : IScopedDependency +{ + Task BeginTransaction(); + Task RollBackAsync(); + Task CommitAsync(); + Task SaveChangesAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Repositories/UnitOfWork/UnitOfWork.cs b/HamyanEdalat.Repository/Repositories/UnitOfWork/UnitOfWork.cs new file mode 100644 index 0000000..2e28eb2 --- /dev/null +++ b/HamyanEdalat.Repository/Repositories/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,61 @@ +namespace HamyanEdalat.Repository.Repositories.UnitOfWork; + +public class UnitOfWork : IUnitOfWork +{ + private readonly ApplicationContext _applicationContext; + private IDbContextTransaction? _currentTransaction ; + public UnitOfWork(ApplicationContext applicationContext) + { + _applicationContext = applicationContext; + } + + public async Task RollBackAsync() + { + if (_currentTransaction == null) + throw new ArgumentNullException(nameof(_currentTransaction)); + await _currentTransaction.RollbackAsync(); + } + public async Task CommitAsync() + { + if (_currentTransaction == null) + throw new ArgumentNullException(nameof(_currentTransaction)); + await _currentTransaction.CommitAsync(); + } + public async Task BeginTransaction() + { + _currentTransaction = await _applicationContext.Database.BeginTransactionAsync(); + } + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + SetAuditables(); + await _applicationContext.SaveChangesAsync(cancellationToken); + } + + private void SetAuditables() + { + IEnumerable> entries = _applicationContext.ChangeTracker.Entries(); + foreach (EntityEntry entity in entries) + { + if (entity.State == EntityState.Added) + { + entity.Property(e=>e.CreatedAt) + .CurrentValue = DateTime.Now; + } + + if (entity.State == EntityState.Modified) + { + entity.Property(e => e.ModifiedAt) + .CurrentValue = DateTime.Now; + } + + if (entity.State == EntityState.Deleted) + { + entity.Property(e => e.RemovedAt) + .CurrentValue = DateTime.Now; + entity.Property(e => e.IsRemoved) + .CurrentValue = true; + entity.State = EntityState.Modified; + } + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/RepositoryConfig.cs b/HamyanEdalat.Repository/RepositoryConfig.cs new file mode 100644 index 0000000..60ce04a --- /dev/null +++ b/HamyanEdalat.Repository/RepositoryConfig.cs @@ -0,0 +1,7 @@ +namespace HamyanEdalat.Repository +{ + public class RepositoryConfig + { + + } +} diff --git a/HamyanEdalat.Repository/Services/Abstracts/IDbInitializerService.cs b/HamyanEdalat.Repository/Services/Abstracts/IDbInitializerService.cs new file mode 100644 index 0000000..cb5e82a --- /dev/null +++ b/HamyanEdalat.Repository/Services/Abstracts/IDbInitializerService.cs @@ -0,0 +1,9 @@ +namespace HamyanEdalat.Repository.Services.Abstracts; + + +public interface IDbInitializerService : IScopedDependency +{ + void Initialize(); + Task SeedDate(bool force = false); + Task SeedRoles(); +} \ No newline at end of file diff --git a/HamyanEdalat.Repository/Services/DbInitializerService.cs b/HamyanEdalat.Repository/Services/DbInitializerService.cs new file mode 100644 index 0000000..1f33127 --- /dev/null +++ b/HamyanEdalat.Repository/Services/DbInitializerService.cs @@ -0,0 +1,145 @@ +namespace HamyanEdalat.Repository.Services; + + +public class DbInitializerService : IDbInitializerService +{ + private readonly IOptionsSnapshot _adminUserSeedOptions; + private readonly ApplicationContext _context; + private readonly ILogger _logger; + private readonly RoleManager _roleManager; + private readonly UserManager _userManager; + + public DbInitializerService( + ApplicationContext context, + RoleManager roleManager, + UserManager userManager, + IOptionsSnapshot adminUserSeedOptions, + ILogger logger) + { + _context = context; + _roleManager = roleManager; + _userManager = userManager; + _adminUserSeedOptions = adminUserSeedOptions; + _logger = logger; + } + + public void Initialize() + { + try + { + _context.Database.Migrate(); + _logger.LogInformation("Migration SUCCESS !!!!"); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + } + } + + public async Task SeedDate(bool force = false) + { + try + { + await SeedRoles(); + + var seedRoot = _adminUserSeedOptions.Value.RootUser; + var rootUser = await _userManager.FindByNameAsync(seedRoot.Username); + if (rootUser == null) + { + rootUser = new ApplicationUser + { + UserName = seedRoot.Username, + Email = seedRoot.Email, + EmailConfirmed = true, + LockoutEnabled = true, + FirstName = seedRoot.FirstName, + LastName = seedRoot.LastName, + Gender = Gender.Male, + PhoneNumberConfirmed = true, + PhoneNumber = seedRoot.Phone, + BirthDate = DateTime.Now.AddYears(-23) + }; + var rootUserResult = await _userManager.CreateAsync(rootUser, seedRoot.Password); + if (rootUserResult.Succeeded) await _userManager.AddToRoleAsync(rootUser, seedRoot.RoleName); + } + + + var seedAdmin = _adminUserSeedOptions.Value.ManagerUser; + var adminUser = await _userManager.FindByNameAsync(seedAdmin.Username); + if (adminUser == null) + { + adminUser = new ApplicationUser + { + UserName = seedAdmin.Username, + Email = seedAdmin.Email, + EmailConfirmed = true, + LockoutEnabled = true, + FirstName = seedAdmin.FirstName, + LastName = seedAdmin.LastName, + Gender = Gender.Male, + PhoneNumberConfirmed = true, + PhoneNumber = seedAdmin.Phone, + BirthDate = DateTime.Now.AddYears(-23) + }; + var rootUserResult = await _userManager.CreateAsync(adminUser, seedAdmin.Password); + if (rootUserResult.Succeeded) await _userManager.AddToRoleAsync(adminUser, seedAdmin.RoleName); + } + + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public async Task SeedRoles() + { + var seedAdmin = _adminUserSeedOptions.Value.RootUser; + var rootRole = await _roleManager.FindByNameAsync(seedAdmin.RoleName); + + if (rootRole == null) + { + rootRole = new ApplicationRole + { + Name = seedAdmin.RoleName, + EnglishName = seedAdmin.RoleName, + Description = "root admin role" + }; + var adminRoleResult = await _roleManager.CreateAsync(rootRole); + foreach (var claim in ApplicationClaims.AllClaims) + await _roleManager.AddClaimAsync(rootRole, claim); + } + + var managerRole = await _roleManager.FindByNameAsync("Manager"); + + if (managerRole == null) + { + managerRole = new ApplicationRole + { + Name = "Manager", + EnglishName = "Manager", + PersianName = "مدیریتـــ", + Description = "admin role" + }; + var adminRoleResult = await _roleManager.CreateAsync(managerRole); + foreach (var claim in ApplicationClaims.AllClaims) + await _roleManager.AddClaimAsync(managerRole, claim); + } + + var customerRole = await _roleManager.FindByNameAsync("Customer"); + if (customerRole == null) + { + customerRole = new ApplicationRole + { + Name = "Customer", + PersianName = "مشتری", + EnglishName = "Customer", + + }; + var customerRoleResult = await _roleManager.CreateAsync(customerRole); + foreach (var claim in ApplicationClaims.CustomerClaims) + await _roleManager.AddClaimAsync(customerRole, claim); + } + } +} \ No newline at end of file diff --git a/HamyanEdalat.sln b/HamyanEdalat.sln index f44eda3..2c14c85 100644 --- a/HamyanEdalat.sln +++ b/HamyanEdalat.sln @@ -5,6 +5,16 @@ VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Api", "HamyanEdalat.Api\HamyanEdalat.Api.csproj", "{FA52F1AF-507E-4F2A-8ACB-FC7B6D275BCA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Common", "HamyanEdalat.Common\HamyanEdalat.Common.csproj", "{BC2E4BF5-2E16-434B-9F5E-6370B1DD7407}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Domain", "HamyanEdalat.Domain\HamyanEdalat.Domain.csproj", "{D2782778-8502-427F-A100-8FB57789BA2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Repository", "HamyanEdalat.Repository\HamyanEdalat.Repository.csproj", "{CF7B3BEE-7B21-454E-BC55-541084CFB003}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Core", "HamyanEdalat.Core\HamyanEdalat.Core.csproj", "{3C777089-75BA-43EF-B796-BFAFABDE14C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HamyanEdalat.Infrastructure", "HamyanEdalat.Infrastructure\HamyanEdalat.Infrastructure.csproj", "{A45A03BC-CD16-427B-9D1F-B6BF48A0C0C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +25,26 @@ Global {FA52F1AF-507E-4F2A-8ACB-FC7B6D275BCA}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA52F1AF-507E-4F2A-8ACB-FC7B6D275BCA}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA52F1AF-507E-4F2A-8ACB-FC7B6D275BCA}.Release|Any CPU.Build.0 = Release|Any CPU + {BC2E4BF5-2E16-434B-9F5E-6370B1DD7407}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC2E4BF5-2E16-434B-9F5E-6370B1DD7407}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC2E4BF5-2E16-434B-9F5E-6370B1DD7407}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC2E4BF5-2E16-434B-9F5E-6370B1DD7407}.Release|Any CPU.Build.0 = Release|Any CPU + {D2782778-8502-427F-A100-8FB57789BA2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2782778-8502-427F-A100-8FB57789BA2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2782778-8502-427F-A100-8FB57789BA2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2782778-8502-427F-A100-8FB57789BA2F}.Release|Any CPU.Build.0 = Release|Any CPU + {CF7B3BEE-7B21-454E-BC55-541084CFB003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF7B3BEE-7B21-454E-BC55-541084CFB003}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF7B3BEE-7B21-454E-BC55-541084CFB003}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF7B3BEE-7B21-454E-BC55-541084CFB003}.Release|Any CPU.Build.0 = Release|Any CPU + {3C777089-75BA-43EF-B796-BFAFABDE14C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C777089-75BA-43EF-B796-BFAFABDE14C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C777089-75BA-43EF-B796-BFAFABDE14C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C777089-75BA-43EF-B796-BFAFABDE14C9}.Release|Any CPU.Build.0 = Release|Any CPU + {A45A03BC-CD16-427B-9D1F-B6BF48A0C0C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A45A03BC-CD16-427B-9D1F-B6BF48A0C0C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A45A03BC-CD16-427B-9D1F-B6BF48A0C0C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A45A03BC-CD16-427B-9D1F-B6BF48A0C0C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE