From ed4410d19176c6ab6266803efb193c0b02448017 Mon Sep 17 00:00:00 2001 From: "Amir.H Khademi" Date: Sat, 16 Dec 2023 20:25:12 +0330 Subject: [PATCH] Add project files. --- .config/dotnet-tools.json | 12 + .dockerignore | 30 + .../Production/appsettings.Production.json | 56 + .../AppSettings/Production/appsettings.json | 9 + .../AppSettings/appsettings.Development.json | 57 + NetinaShop.Api/AppSettings/appsettings.json | 9 + NetinaShop.Api/Controller/AuthController.cs | 63 + NetinaShop.Api/Controller/BlogController.cs | 74 + NetinaShop.Api/Dockerfile | 24 + NetinaShop.Api/NetinaShop.Api.csproj | 124 ++ NetinaShop.Api/NetinaShop.Api.http | 2 + NetinaShop.Api/Program.cs | 109 ++ NetinaShop.Api/Properties/launchSettings.json | 41 + .../Bases/ApiResultFilterAttribute.cs | 74 + .../WebFramework/Bases/ClaimRequirement.cs | 32 + .../WebFramework/Bases/CrudController.cs | 218 +++ .../ConfigureJwtBearerOptions.cs | 23 + .../Configurations/LoggerConfig.cs | 19 + .../PersianIdentityErrorDescriber.cs | 134 ++ .../Configurations/ServiceExtensions.cs | 180 ++ .../MiddleWares/ExceptionHandlerMiddleware.cs | 207 +++ .../MiddleWares/PerformanceMiddleware.cs | 35 + .../Swagger/SwaggerConfiguration.cs | 279 +++ NetinaShop.Common/CommonConfig.cs | 7 + .../Extensions/AssertExtensions.cs | 30 + .../Extensions/ClassDisplayExtensions.cs | 39 + .../Extensions/DateTimeExtensions.cs | 59 + .../Extensions/EnumExtensions.cs | 55 + .../Extensions/NewtonJsonExtensions.cs | 15 + .../Extensions/PhoneNumberExtensions.cs | 41 + .../Extensions/PropertyExtensions.cs | 17 + .../Extensions/RandomExtensions.cs | 12 + .../Extensions/StringExtensions.cs | 165 ++ .../Extensions/ValidationExtensions.cs | 21 + NetinaShop.Common/Models/Api/AccessToken.cs | 75 + .../Models/Api/ApiResultStatusCode.cs | 42 + NetinaShop.Common/Models/Api/AppSettings.cs | 7 + .../Models/Api/FileUploadRequest.cs | 15 + NetinaShop.Common/Models/Api/HealthCheck.cs | 12 + NetinaShop.Common/Models/Api/ResponseFile.cs | 9 + NetinaShop.Common/Models/Api/TokenRequest.cs | 18 + NetinaShop.Common/Models/Entity/ApiEntity.cs | 48 + .../Models/Entity/ClassDisplay.cs | 25 + NetinaShop.Common/Models/Entity/IApiEntity.cs | 13 + .../Models/Exception/AppException.cs | 26 + .../Models/Exception/BaseApiException.cs | 91 + NetinaShop.Common/Models/IScopedDependency.cs | 6 + NetinaShop.Common/Models/Mapper/BaseDto.cs | 97 + NetinaShop.Common/Models/Mapper/IBaseDto.cs | 11 + NetinaShop.Common/Models/Report/ChartUnit.cs | 38 + .../Models/Report/ReportRequest.cs | 24 + .../Models/Report/ReportResult.cs | 34 + .../Models/Report/ReportableItem.cs | 89 + NetinaShop.Common/NetinaShop.Common.csproj | 40 + NetinaShop.Core/Abstracts/ISmsService.cs | 7 + .../BaseServices/Abstracts/IJwtService.cs | 12 + NetinaShop.Core/BaseServices/JwtService.cs | 151 ++ NetinaShop.Core/CoreConfig.cs | 7 + .../CoreServices/Abstracts/IAccountService.cs | 11 + .../CoreServices/AccountService.cs | 156 ++ .../EntityServices/Abstracts/IUserService.cs | 21 + NetinaShop.Core/EntityServices/UserService.cs | 314 ++++ NetinaShop.Core/Models/Api/ApiResult.cs | 126 ++ NetinaShop.Core/NetinaShop.Core.csproj | 63 + NetinaShop.Domain/DomainConfig.cs | 7 + .../Dtos/RequestDtos/LoginRequestDto.cs | 8 + .../Dtos/RequestDtos/RoleActionRequestDto.cs | 10 + .../Dtos/RequestDtos/SignUpRequestDto.cs | 7 + .../Dtos/RequestDtos/UserActionRequestDto.cs | 14 + .../Dtos/ResponseDtos/ProfileResponseDto.cs | 8 + .../ResponseDtos/VerifyCodeResponseDto.cs | 7 + .../Dtos/SmallDtos/ApplicationUserSDto.cs | 16 + NetinaShop.Domain/Entities/Blogs/Blog.cs | 15 + .../Entities/Blogs/BlogCategory.cs | 8 + .../Entities/Blogs/BlogStorageFile.cs | 9 + NetinaShop.Domain/Entities/Brands/Brand.cs | 13 + .../Entities/Brands/BrandStorageFile.cs | 9 + .../Entities/Categories/Category.cs | 16 + .../Categories/CategoryStorageFile.cs | 9 + .../Entities/Discounts/CategoryDiscount.cs | 7 + .../Entities/Discounts/Discount.cs | 23 + .../Entities/Discounts/ProductDiscount.cs | 7 + NetinaShop.Domain/Entities/Orders/Order.cs | 20 + .../Entities/Orders/OrderDelivery.cs | 11 + .../Entities/Orders/OrderProduct.cs | 17 + .../Entities/Products/Product.cs | 23 + .../Entities/Products/ProductCategory.cs | 11 + .../Entities/Products/ProductStorageFile.cs | 9 + NetinaShop.Domain/Entities/Products/Review.cs | 17 + .../Entities/Products/Specification.cs | 16 + .../Entities/StorageFiles/StorageFile.cs | 11 + .../Entities/Users/ApplicationRole.cs | 8 + .../Entities/Users/ApplicationUser.cs | 18 + .../Entities/Users/UserAddress.cs | 14 + .../Entities/Users/UserFavoriteProduct.cs | 9 + .../Entities/Warehouses/Shipping.cs | 10 + NetinaShop.Domain/Enums/DiscountAmountType.cs | 9 + NetinaShop.Domain/Enums/DiscountType.cs | 13 + NetinaShop.Domain/Enums/Gender.cs | 7 + NetinaShop.Domain/Enums/OrderStatus.cs | 22 + NetinaShop.Domain/Enums/SignUpStatus.cs | 8 + NetinaShop.Domain/Enums/StorageFileType.cs | 7 + NetinaShop.Domain/FodyWeavers.xml | 3 + .../Mappers/ApplicationUserMapper.g.cs | 88 + NetinaShop.Domain/MappingRegister.cs | 11 + .../Models/Claims/ApplicationClaims.cs | 221 +++ .../Models/Claims/ApplicationPermission.cs | 41 + NetinaShop.Domain/Models/Claims/ClaimDto.cs | 20 + .../Models/Claims/CustomClaimType.cs | 7 + .../Models/Settings/SiteSettings.cs | 37 + NetinaShop.Domain/NetinaShop.Domain.csproj | 77 + .../InfrastructureConfig.cs | 7 + .../Models/DirectoryAddress.cs | 7 + .../NetinaShop.Infrastructure.csproj | 24 + .../Abstracts/ICurrentUserService.cs | 8 + .../DbContextOptionCustomExtensionsInfo.cs | 59 + .../Extensions/ModelBuilderExtensions.cs | 190 ++ .../20231211073413_init.Designer.cs | 1601 +++++++++++++++++ .../Migrations/20231211073413_init.cs | 1020 +++++++++++ .../ApplicationContextModelSnapshot.cs | 1598 ++++++++++++++++ .../Models/ApplicationContext.cs | 47 + .../NetinaShop.Repository.csproj | 71 + .../Repositories/Base/BaseRepository.cs | 109 ++ .../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 | 43 + .../Repositories/Base/Repository.cs | 36 + .../Repositories/Base/RepositoryWrapper.cs | 64 + .../Repositories/Base/WriteRepository.cs | 95 + .../Repositories/UnitOfWork/IUnitOfWork.cs | 9 + .../Repositories/UnitOfWork/UnitOfWork.cs | 61 + NetinaShop.Repository/RepositoryConfig.cs | 18 + .../Abstracts/IDbInitializerService.cs | 9 + .../Services/DbInitializerService.cs | 105 ++ NetinaShop.sln | 55 + 138 files changed, 10109 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 .dockerignore create mode 100644 NetinaShop.Api/AppSettings/Production/appsettings.Production.json create mode 100644 NetinaShop.Api/AppSettings/Production/appsettings.json create mode 100644 NetinaShop.Api/AppSettings/appsettings.Development.json create mode 100644 NetinaShop.Api/AppSettings/appsettings.json create mode 100644 NetinaShop.Api/Controller/AuthController.cs create mode 100644 NetinaShop.Api/Controller/BlogController.cs create mode 100644 NetinaShop.Api/Dockerfile create mode 100644 NetinaShop.Api/NetinaShop.Api.csproj create mode 100644 NetinaShop.Api/NetinaShop.Api.http create mode 100644 NetinaShop.Api/Program.cs create mode 100644 NetinaShop.Api/Properties/launchSettings.json create mode 100644 NetinaShop.Api/WebFramework/Bases/ApiResultFilterAttribute.cs create mode 100644 NetinaShop.Api/WebFramework/Bases/ClaimRequirement.cs create mode 100644 NetinaShop.Api/WebFramework/Bases/CrudController.cs create mode 100644 NetinaShop.Api/WebFramework/Configurations/ConfigureJwtBearerOptions.cs create mode 100644 NetinaShop.Api/WebFramework/Configurations/LoggerConfig.cs create mode 100644 NetinaShop.Api/WebFramework/Configurations/PersianIdentityErrorDescriber.cs create mode 100644 NetinaShop.Api/WebFramework/Configurations/ServiceExtensions.cs create mode 100644 NetinaShop.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs create mode 100644 NetinaShop.Api/WebFramework/MiddleWares/PerformanceMiddleware.cs create mode 100644 NetinaShop.Api/WebFramework/Swagger/SwaggerConfiguration.cs create mode 100644 NetinaShop.Common/CommonConfig.cs create mode 100644 NetinaShop.Common/Extensions/AssertExtensions.cs create mode 100644 NetinaShop.Common/Extensions/ClassDisplayExtensions.cs create mode 100644 NetinaShop.Common/Extensions/DateTimeExtensions.cs create mode 100644 NetinaShop.Common/Extensions/EnumExtensions.cs create mode 100644 NetinaShop.Common/Extensions/NewtonJsonExtensions.cs create mode 100644 NetinaShop.Common/Extensions/PhoneNumberExtensions.cs create mode 100644 NetinaShop.Common/Extensions/PropertyExtensions.cs create mode 100644 NetinaShop.Common/Extensions/RandomExtensions.cs create mode 100644 NetinaShop.Common/Extensions/StringExtensions.cs create mode 100644 NetinaShop.Common/Extensions/ValidationExtensions.cs create mode 100644 NetinaShop.Common/Models/Api/AccessToken.cs create mode 100644 NetinaShop.Common/Models/Api/ApiResultStatusCode.cs create mode 100644 NetinaShop.Common/Models/Api/AppSettings.cs create mode 100644 NetinaShop.Common/Models/Api/FileUploadRequest.cs create mode 100644 NetinaShop.Common/Models/Api/HealthCheck.cs create mode 100644 NetinaShop.Common/Models/Api/ResponseFile.cs create mode 100644 NetinaShop.Common/Models/Api/TokenRequest.cs create mode 100644 NetinaShop.Common/Models/Entity/ApiEntity.cs create mode 100644 NetinaShop.Common/Models/Entity/ClassDisplay.cs create mode 100644 NetinaShop.Common/Models/Entity/IApiEntity.cs create mode 100644 NetinaShop.Common/Models/Exception/AppException.cs create mode 100644 NetinaShop.Common/Models/Exception/BaseApiException.cs create mode 100644 NetinaShop.Common/Models/IScopedDependency.cs create mode 100644 NetinaShop.Common/Models/Mapper/BaseDto.cs create mode 100644 NetinaShop.Common/Models/Mapper/IBaseDto.cs create mode 100644 NetinaShop.Common/Models/Report/ChartUnit.cs create mode 100644 NetinaShop.Common/Models/Report/ReportRequest.cs create mode 100644 NetinaShop.Common/Models/Report/ReportResult.cs create mode 100644 NetinaShop.Common/Models/Report/ReportableItem.cs create mode 100644 NetinaShop.Common/NetinaShop.Common.csproj create mode 100644 NetinaShop.Core/Abstracts/ISmsService.cs create mode 100644 NetinaShop.Core/BaseServices/Abstracts/IJwtService.cs create mode 100644 NetinaShop.Core/BaseServices/JwtService.cs create mode 100644 NetinaShop.Core/CoreConfig.cs create mode 100644 NetinaShop.Core/CoreServices/Abstracts/IAccountService.cs create mode 100644 NetinaShop.Core/CoreServices/AccountService.cs create mode 100644 NetinaShop.Core/EntityServices/Abstracts/IUserService.cs create mode 100644 NetinaShop.Core/EntityServices/UserService.cs create mode 100644 NetinaShop.Core/Models/Api/ApiResult.cs create mode 100644 NetinaShop.Core/NetinaShop.Core.csproj create mode 100644 NetinaShop.Domain/DomainConfig.cs create mode 100644 NetinaShop.Domain/Dtos/RequestDtos/LoginRequestDto.cs create mode 100644 NetinaShop.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs create mode 100644 NetinaShop.Domain/Dtos/RequestDtos/SignUpRequestDto.cs create mode 100644 NetinaShop.Domain/Dtos/RequestDtos/UserActionRequestDto.cs create mode 100644 NetinaShop.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs create mode 100644 NetinaShop.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs create mode 100644 NetinaShop.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs create mode 100644 NetinaShop.Domain/Entities/Blogs/Blog.cs create mode 100644 NetinaShop.Domain/Entities/Blogs/BlogCategory.cs create mode 100644 NetinaShop.Domain/Entities/Blogs/BlogStorageFile.cs create mode 100644 NetinaShop.Domain/Entities/Brands/Brand.cs create mode 100644 NetinaShop.Domain/Entities/Brands/BrandStorageFile.cs create mode 100644 NetinaShop.Domain/Entities/Categories/Category.cs create mode 100644 NetinaShop.Domain/Entities/Categories/CategoryStorageFile.cs create mode 100644 NetinaShop.Domain/Entities/Discounts/CategoryDiscount.cs create mode 100644 NetinaShop.Domain/Entities/Discounts/Discount.cs create mode 100644 NetinaShop.Domain/Entities/Discounts/ProductDiscount.cs create mode 100644 NetinaShop.Domain/Entities/Orders/Order.cs create mode 100644 NetinaShop.Domain/Entities/Orders/OrderDelivery.cs create mode 100644 NetinaShop.Domain/Entities/Orders/OrderProduct.cs create mode 100644 NetinaShop.Domain/Entities/Products/Product.cs create mode 100644 NetinaShop.Domain/Entities/Products/ProductCategory.cs create mode 100644 NetinaShop.Domain/Entities/Products/ProductStorageFile.cs create mode 100644 NetinaShop.Domain/Entities/Products/Review.cs create mode 100644 NetinaShop.Domain/Entities/Products/Specification.cs create mode 100644 NetinaShop.Domain/Entities/StorageFiles/StorageFile.cs create mode 100644 NetinaShop.Domain/Entities/Users/ApplicationRole.cs create mode 100644 NetinaShop.Domain/Entities/Users/ApplicationUser.cs create mode 100644 NetinaShop.Domain/Entities/Users/UserAddress.cs create mode 100644 NetinaShop.Domain/Entities/Users/UserFavoriteProduct.cs create mode 100644 NetinaShop.Domain/Entities/Warehouses/Shipping.cs create mode 100644 NetinaShop.Domain/Enums/DiscountAmountType.cs create mode 100644 NetinaShop.Domain/Enums/DiscountType.cs create mode 100644 NetinaShop.Domain/Enums/Gender.cs create mode 100644 NetinaShop.Domain/Enums/OrderStatus.cs create mode 100644 NetinaShop.Domain/Enums/SignUpStatus.cs create mode 100644 NetinaShop.Domain/Enums/StorageFileType.cs create mode 100644 NetinaShop.Domain/FodyWeavers.xml create mode 100644 NetinaShop.Domain/Mappers/ApplicationUserMapper.g.cs create mode 100644 NetinaShop.Domain/MappingRegister.cs create mode 100644 NetinaShop.Domain/Models/Claims/ApplicationClaims.cs create mode 100644 NetinaShop.Domain/Models/Claims/ApplicationPermission.cs create mode 100644 NetinaShop.Domain/Models/Claims/ClaimDto.cs create mode 100644 NetinaShop.Domain/Models/Claims/CustomClaimType.cs create mode 100644 NetinaShop.Domain/Models/Settings/SiteSettings.cs create mode 100644 NetinaShop.Domain/NetinaShop.Domain.csproj create mode 100644 NetinaShop.Infrastructure/InfrastructureConfig.cs create mode 100644 NetinaShop.Infrastructure/Models/DirectoryAddress.cs create mode 100644 NetinaShop.Infrastructure/NetinaShop.Infrastructure.csproj create mode 100644 NetinaShop.Repository/Abstracts/ICurrentUserService.cs create mode 100644 NetinaShop.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs create mode 100644 NetinaShop.Repository/Extensions/ModelBuilderExtensions.cs create mode 100644 NetinaShop.Repository/Migrations/20231211073413_init.Designer.cs create mode 100644 NetinaShop.Repository/Migrations/20231211073413_init.cs create mode 100644 NetinaShop.Repository/Migrations/ApplicationContextModelSnapshot.cs create mode 100644 NetinaShop.Repository/Models/ApplicationContext.cs create mode 100644 NetinaShop.Repository/NetinaShop.Repository.csproj create mode 100644 NetinaShop.Repository/Repositories/Base/BaseRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Contracts/IBaseRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Contracts/IReadRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Contracts/IRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Contracts/IWriteRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/ReadRepository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/Repository.cs create mode 100644 NetinaShop.Repository/Repositories/Base/RepositoryWrapper.cs create mode 100644 NetinaShop.Repository/Repositories/Base/WriteRepository.cs create mode 100644 NetinaShop.Repository/Repositories/UnitOfWork/IUnitOfWork.cs create mode 100644 NetinaShop.Repository/Repositories/UnitOfWork/UnitOfWork.cs create mode 100644 NetinaShop.Repository/RepositoryConfig.cs create mode 100644 NetinaShop.Repository/Services/Abstracts/IDbInitializerService.cs create mode 100644 NetinaShop.Repository/Services/DbInitializerService.cs create mode 100644 NetinaShop.sln 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/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/NetinaShop.Api/AppSettings/Production/appsettings.Production.json b/NetinaShop.Api/AppSettings/Production/appsettings.Production.json new file mode 100644 index 0000000..1732965 --- /dev/null +++ b/NetinaShop.Api/AppSettings/Production/appsettings.Production.json @@ -0,0 +1,56 @@ +{ + "ConnectionStrings": { + "Postgres": "Host=pg-0;port=5432;Username=postgres;Password=ub0J7sFFThkSBmkc0TzSKsCfheRnQpyu;Database=NetinaShopDB" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "None", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.AspNetCore.SignalR": "Debug", + "Microsoft.AspNetCore.Http.Connections": "Debug" + } + }, + "SiteSettings": { + "BaseUrl": "https://api.brizco.io", + "KaveNegarApiKey": "3735494B4143727A794346457461576A2B4B6668414973424E333561505A694B", + "UserSetting": { + "Username": "netinashop", + "Email": "info@netinashop.io", + "Password": "eF79o4P4BopCUbUK", + "Phone": "09211111111", + "RoleName": "RootAdmin", + "FirstName": "همه کاره", + "LastName": "سیستم" + }, + "JwtSettings": { + "SecretKey": "YAEMAMZAMAN_KHODET_NEGAHDAR_IN_KEY_BASH_nw+8E0EABj0Wg8c4mHg/bDBf5qGMhmBPb6u16DVe9/MzYva1e+/J1zImyIoQX2Lmra2kvzsIjGiwP7r3Znd_YA_JADE_NASABE_v+Ro/CDixScDv6EkpZnkBv9MFdPnSmFXNGMH9gA1BzQUoC1iSX9Aq+pMIw/cMKXI9WA==_YA_HUSEIN_SEYED_SHOHADA_BE_OMID_KHODET", + "Issuer": "NetinaShop", + "Audience": "NetinaShop", + "ExpireAddDay": "15" + } + }, + "IpRateLimiting": { + "EnableEndpointRateLimiting": false, + "StackBlockedRequests": false, + "RealIpHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + "IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ], + "EndpointWhitelist": [ "get:/api/license", "*:/api/status" ], + "ClientWhitelist": [ "dev-id-1", "dev-id-2" ], + "GeneralRules": [ + { + "Endpoint": "*", + "Period": "1m", + "Limit": 60 + }, + { + "Endpoint": "*", + "Period": "15m", + "Limit": 250 + } + ] + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/NetinaShop.Api/AppSettings/Production/appsettings.json b/NetinaShop.Api/AppSettings/Production/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/NetinaShop.Api/AppSettings/Production/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/NetinaShop.Api/AppSettings/appsettings.Development.json b/NetinaShop.Api/AppSettings/appsettings.Development.json new file mode 100644 index 0000000..6d1ab7b --- /dev/null +++ b/NetinaShop.Api/AppSettings/appsettings.Development.json @@ -0,0 +1,57 @@ +{ + "ConnectionStrings": { + "PostgresServer": "User ID=postgres;Password=root;Host=localhost;Port=5432;Database=iGarsonDB;", + "Postgres": "Host=pg-0,pg-1;Username=igarsonAgent;Password=xHTpBf4wC+bBeNg2pL6Ga7VEWKFJx7VPEUpqxwPFfOc2YYTVwFQuHfsiqoVeT9+6;Database=NetinaShopDB;Load Balance Hosts=true;Target Session Attributes=primary;Application Name=iGLS" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "None", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.AspNetCore.SignalR": "Debug", + "Microsoft.AspNetCore.Http.Connections": "Debug" + } + }, + "SiteSettings": { + "BaseUrl": "http://localhost:32770", + "KaveNegarApiKey": "3735494B4143727A794346457461576A2B4B6668414973424E333561505A694B", + "UserSetting": { + "Username": "netinashop", + "Email": "info@netinashop.io", + "Password": "eF79o4P4BopCUbUK", + "Phone": "09211111111", + "RoleName": "RootAdmin", + "FirstName": "همه کاره", + "LastName": "سیستم" + }, + "JwtSettings": { + "SecretKey": "YAEMAMZAMAN_KHODET_NEGAHDAR_IN_KEY_BASH_nw+8E0EABj0Wg8c4mHg/bDBf5qGMhmBPb6u16DVe9/MzYva1e+/J1zImyIoQX2Lmra2kvzsIjGiwP7r3Znd_YA_JADE_NASABE_v+Ro/CDixScDv6EkpZnkBv9MFdPnSmFXNGMH9gA1BzQUoC1iSX9Aq+pMIw/cMKXI9WA==_YA_HUSEIN_SEYED_SHOHADA_BE_OMID_KHODET", + "Issuer": "Brizco", + "Audience": "Brizco", + "ExpireAddDay": "15" + } + }, + "IpRateLimiting": { + "EnableEndpointRateLimiting": false, + "StackBlockedRequests": false, + "RealIpHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + "IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ], + "EndpointWhitelist": [ "get:/api/license", "*:/api/status" ], + "ClientWhitelist": [ "dev-id-1", "dev-id-2" ], + "GeneralRules": [ + { + "Endpoint": "*", + "Period": "1m", + "Limit": 60 + }, + { + "Endpoint": "*", + "Period": "15m", + "Limit": 250 + } + ] + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/NetinaShop.Api/AppSettings/appsettings.json b/NetinaShop.Api/AppSettings/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/NetinaShop.Api/AppSettings/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/NetinaShop.Api/Controller/AuthController.cs b/NetinaShop.Api/Controller/AuthController.cs new file mode 100644 index 0000000..2c8955e --- /dev/null +++ b/NetinaShop.Api/Controller/AuthController.cs @@ -0,0 +1,63 @@ +namespace NetinaShop.Api.Controller; + +public class AuthController : ICarterModule +{ + + public virtual void AddRoutes(IEndpointRouteBuilder app) + { + var group = app.NewVersionedApi("Auth") + .MapGroup($"api/auth"); + + group.MapPost("login/password", LoginWithPassword) + .WithDisplayName("LoginWithPassword") + .HasApiVersion(1.0); + group.MapPost("login/swagger", LoginSwagger) + .WithDisplayName("LoginSwagger") + .HasApiVersion(1.0); + + group.MapPost("login/code", LoginWithVerifyCode) + .WithDisplayName("LoginWithVerifyCode") + .HasApiVersion(1.0); + + group.MapGet("verifycode", GetVerifyCodeCode) + .WithDisplayName("GetVerifyCodeCode") + .HasApiVersion(1.0); + + group.MapPut("forget/password", ForgetPassword) + .WithDisplayName("ForgetPassword") + .HasApiVersion(1.0); + + group.MapPost("signup", SignUpComplex) + .WithDisplayName("SignUp") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser()) + .HasApiVersion(1.0); + } + + public async Task SignUpComplex([FromBody] SignUpRequestDto request, IAccountService accountService, CancellationToken cancellationToken) => + TypedResults.Ok(await accountService.CompleteSignUpAsync(request, cancellationToken)); + + public async Task LoginWithPassword([FromBody] LoginRequestDto loginRequestDto, IAccountService accountService, CancellationToken cancellationToken) => + TypedResults.Ok(await accountService.LoginWithPasswordAsync(loginRequestDto.UserName, loginRequestDto.Password, cancellationToken)); + + + public async Task LoginWithVerifyCode([FromBody] LoginRequestDto loginRequestDto, IAccountService accountService, CancellationToken cancellationToken) => + TypedResults.Ok(await accountService.LoginWithVerifyCodeAsync(loginRequestDto.UserName, loginRequestDto.VerifyCode, cancellationToken)); + + + public async Task GetVerifyCodeCode([FromQuery] string phoneNumber, IAccountService accountService) => + TypedResults.Ok(await accountService.GetVerifyCodeAsync(phoneNumber)); + + + public async Task ForgetPassword([FromQuery] string phoneNumber, IAccountService accountService) => + TypedResults.Ok(await accountService.ForgetPasswordAsync(phoneNumber)); + + + public async Task LoginSwagger(HttpContext ctx, IAccountService accountService, CancellationToken cancellationToken) + { + + var username = ctx.Request.Form["username"]; + var password = ctx.Request.Form["password"]; + return TypedResults.Json(await accountService.LoginWithPasswordAsync(username, password, cancellationToken)); + } + +} \ No newline at end of file diff --git a/NetinaShop.Api/Controller/BlogController.cs b/NetinaShop.Api/Controller/BlogController.cs new file mode 100644 index 0000000..cc97f8b --- /dev/null +++ b/NetinaShop.Api/Controller/BlogController.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using NetinaShop.Domain.Entities.Blogs; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +namespace NetinaShop.Api.Controller; + +public class BlogController : ICarterModule +{ + + public virtual void AddRoutes(IEndpointRouteBuilder app) + { + var group = app.NewVersionedApi("Blog") + .MapGroup($"api/blog") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser()); + + group.MapGet("", GetAllAsync) + .WithDisplayName("GetAllBlogs") + .HasApiVersion(1.0); + + group.MapGet("{id}", GetAsync) + .WithDisplayName("GetBlog") + .HasApiVersion(1.0); + + group.MapPost("", Post) + .HasApiVersion(1.0); + + group.MapPut("", Put) + .HasApiVersion(1.0); + + group.MapDelete("{id}", Delete) + .HasApiVersion(1.0); + } + + // GET:Get All Entity + public async Task GetAllAsync([FromQuery] int page, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + => TypedResults.Ok(await repositoryWrapper.SetRepository().TableNoTracking.OrderByDescending(b=>b.CreatedAt).Skip(page*10).Take(10).ToListAsync(cancellationToken)); + + // GET:Get An Entity By Id + public async Task GetAsync(Guid id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + => TypedResults.Ok(await repositoryWrapper.SetRepository().TableNoTracking.Where(b=>b.Id==id).FirstOrDefaultAsync(cancellationToken)); + + // POST:Create Entity + public async Task Post([FromBody] Blog ent, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + { + repositoryWrapper.SetRepository().Add(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + return TypedResults.Ok(); + } + + // PUT:Update Entity + public async Task Put([FromBody] Blog updated, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + { + var ent = await repositoryWrapper.SetRepository().TableNoTracking + .FirstOrDefaultAsync(b => b.Id == updated.Id, cancellationToken); + if (ent == null) + throw new AppException("Blog not found"); + repositoryWrapper.SetRepository().Update(updated); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + return TypedResults.Ok(); + } + + // DELETE:Delete Entity + public async Task Delete(Guid id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + { + var ent = await repositoryWrapper.SetRepository().TableNoTracking + .FirstOrDefaultAsync(b => b.Id == id, cancellationToken); + if (ent == null) + throw new AppException("Blog not found"); + repositoryWrapper.SetRepository().Delete(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + return TypedResults.Ok(); + } + +} \ No newline at end of file diff --git a/NetinaShop.Api/Dockerfile b/NetinaShop.Api/Dockerfile new file mode 100644 index 0000000..599a2c4 --- /dev/null +++ b/NetinaShop.Api/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["NetinaShop.Api/NetinaShop.Api.csproj", "NetinaShop.Api/"] +RUN dotnet restore "./NetinaShop.Api/./NetinaShop.Api.csproj" +COPY . . +WORKDIR "/src/NetinaShop.Api" +RUN dotnet build "./NetinaShop.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./NetinaShop.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "NetinaShop.Api.dll"] \ No newline at end of file diff --git a/NetinaShop.Api/NetinaShop.Api.csproj b/NetinaShop.Api/NetinaShop.Api.csproj new file mode 100644 index 0000000..975f531 --- /dev/null +++ b/NetinaShop.Api/NetinaShop.Api.csproj @@ -0,0 +1,124 @@ + + + + net8.0 + enable + enable + true + Linux + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Api/NetinaShop.Api.http b/NetinaShop.Api/NetinaShop.Api.http new file mode 100644 index 0000000..b5286a6 --- /dev/null +++ b/NetinaShop.Api/NetinaShop.Api.http @@ -0,0 +1,2 @@ +@NetinaShop.Api_HostAddress = http://localhost:5173 + diff --git a/NetinaShop.Api/Program.cs b/NetinaShop.Api/Program.cs new file mode 100644 index 0000000..ea7295d --- /dev/null +++ b/NetinaShop.Api/Program.cs @@ -0,0 +1,109 @@ +var builder = WebApplication.CreateBuilder(args); +builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); +builder.Host.UseSerilog(); +LoggerConfig.ConfigureSerilog(); +string env = builder.Environment.IsDevelopment() == true ? "Development" : "Production"; +builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); +if (builder.Environment.IsDevelopment()) + builder.Configuration + .AddJsonFile($"AppSettings/appsettings.json") + .AddJsonFile($"AppSettings/appsettings.{env}.json"); + +if (builder.Environment.IsProduction()) + builder.Configuration.AddJsonFile($"AppSettings/Production/appsettings.{env}.json"); +var configuration = builder.Configuration; +var siteSetting = configuration.GetSection(nameof(SiteSettings)).Get(); +builder.Services.Configure(configuration.GetSection(nameof(SiteSettings))); + + + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddCustomSwagger(siteSetting!.BaseUrl); +builder.Services.AddCustomApiVersioning(); +builder.Services.AddCustomController(); +builder.Services.AddControllers(); +builder.Services.AddCustomResponseCompression(); +builder.Services.AddValidatorsFromAssembly(typeof(RepositoryConfig).Assembly, includeInternalTypes: true); +builder.Services.AddCustomMvc(); +builder.Services.AddCustomAuthorization(); +builder.Services.AddJwtCustomAuthentication(siteSetting.JwtSettings); +builder.Services.AddMvcCore().AddRazorPages().AddRazorViewEngine().AddViews(); +builder.Services.AddCustomIdentity(); +builder.Services.AddCustomDbContext(configuration); +builder.Services.AddCarter(); + + +builder.Host.ConfigureContainer(builder => +{ + + var assembly = typeof(CoreConfig).Assembly; + builder + .RegisterAssemblyTypes(assembly) + .AssignableTo() + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + var assemblyB = typeof(InfrastructureConfig).Assembly; + builder.RegisterAssemblyTypes(assemblyB) + .AssignableTo() + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + var assemblyC = typeof(RepositoryConfig).Assembly; + builder.RegisterAssemblyTypes(assemblyC) + .AssignableTo() + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + + var assemblyD = typeof(Program).Assembly; + builder.RegisterAssemblyTypes(assemblyD) + .AssignableTo() + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + builder.RegisterMediatR(MediatRConfigurationBuilder + .Create(typeof(RepositoryConfig).Assembly) + .WithAllOpenGenericHandlerTypesRegistered() + .Build()); + + builder.RegisterMediatR(MediatRConfigurationBuilder + .Create(typeof(DomainConfig).Assembly) + .WithAllOpenGenericHandlerTypesRegistered() + .Build()); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseCustomSwagger(siteSetting.BaseUrl); + //app.UseSwagger(); + //app.UseSwaggerUI(); +} +//app.UseCustomSwagger(siteSetting.BaseUrl); + +app.UseAuthorization(); +app.UseAuthentication(); + +app.UseCors(x => x + .SetIsOriginAllowed(origin => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + +app.UseExceptionHandlerMiddleware(); + +app.MapCarter(); +app.UseStaticFiles(); +await app.InitialDb(); +app.MapControllers(); + +app.Run(); + + diff --git a/NetinaShop.Api/Properties/launchSettings.json b/NetinaShop.Api/Properties/launchSettings.json new file mode 100644 index 0000000..26842b5 --- /dev/null +++ b/NetinaShop.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5173" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_URLS": "http://+:80" + }, + "publishAllPorts": true, + "DockerfileRunArguments": " --network=mother -p 32770:80" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36260", + "sslPort": 0 + } + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Bases/ApiResultFilterAttribute.cs b/NetinaShop.Api/WebFramework/Bases/ApiResultFilterAttribute.cs new file mode 100644 index 0000000..8ab519d --- /dev/null +++ b/NetinaShop.Api/WebFramework/Bases/ApiResultFilterAttribute.cs @@ -0,0 +1,74 @@ +namespace NetinaShop.Api.WebFramework.Bases; + +public class ApiResultFactory +{ + +} +public class ApiResultFilterAttribute : ActionFilterAttribute +{ + public override void OnResultExecuting(ResultExecutingContext context) + { + if (context.Result is OkObjectResult okObjectResult) + { + var apiResult = new ApiResult(true, ApiResultStatusCode.Success, okObjectResult.Value); + context.Result = new JsonResult(apiResult) { StatusCode = okObjectResult.StatusCode }; + } + else if (context.Result is OkResult okResult) + { + var apiResult = new ApiResult(true, ApiResultStatusCode.Success); + context.Result = new JsonResult(apiResult) { StatusCode = okResult.StatusCode }; + } + else if (context.Result is BadRequestResult badRequestResult) + { + var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest); + context.Result = new JsonResult(apiResult) { StatusCode = badRequestResult.StatusCode }; + } + else if (context.Result is BadRequestObjectResult badRequestObjectResult) + { + var message = badRequestObjectResult.Value.ToString(); + + if (badRequestObjectResult.Value is SerializableError errors) + { + var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct(); + message = string.Join(" | ", errorMessages); + } + + if (badRequestObjectResult.Value is ValidationProblemDetails problemDetails) + { + var errorMessages = problemDetails.Errors.Values.SelectMany(v => v); + message = string.Join(" | ", errorMessages); + } + + var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest, message); + context.Result = new JsonResult(apiResult) { StatusCode = badRequestObjectResult.StatusCode }; + } + else if (context.Result is ContentResult contentResult) + { + var apiResult = new ApiResult(true, ApiResultStatusCode.Success, contentResult.Content); + context.Result = new JsonResult(apiResult) { StatusCode = contentResult.StatusCode }; + } + else if (context.Result is NotFoundResult notFoundResult) + { + var apiResult = new ApiResult(false, ApiResultStatusCode.NotFound); + context.Result = new JsonResult(apiResult) { StatusCode = notFoundResult.StatusCode }; + } + else if (context.Result is NotFoundObjectResult notFoundObjectResult) + { + var apiResult = new ApiResult(false, ApiResultStatusCode.NotFound, notFoundObjectResult.Value); + context.Result = new JsonResult(apiResult) { StatusCode = notFoundObjectResult.StatusCode }; + } + else if (context.Result is ObjectResult objectResult && objectResult.StatusCode == null + && !(objectResult.Value is ApiResult)) + { + var apiResult = new ApiResult(true, ApiResultStatusCode.Success, objectResult.Value); + context.Result = new JsonResult(apiResult) { StatusCode = objectResult.StatusCode }; + } + else if (context.Result is ObjectResult objectResultBad && objectResultBad.Value is ApiResult) + { + var apiResult = objectResultBad.Value as ApiResult; + context.Result = new JsonResult(apiResult) { StatusCode = objectResultBad.StatusCode }; + } + + base.OnResultExecuting(context); + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Bases/ClaimRequirement.cs b/NetinaShop.Api/WebFramework/Bases/ClaimRequirement.cs new file mode 100644 index 0000000..eab8549 --- /dev/null +++ b/NetinaShop.Api/WebFramework/Bases/ClaimRequirement.cs @@ -0,0 +1,32 @@ +namespace NetinaShop.Api.WebFramework.Bases; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public class ClaimRequirement : AuthorizeAttribute, IAuthorizationFilter +{ + private readonly string _claimsType; + private readonly string _claimsValue; + + public ClaimRequirement(string type,string value) + { + type = value; + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + var user = context.HttpContext.User; + var permissions = user.Claims?.Where(c => c.Type == _claimsType)?.ToList(); + if (permissions == null) + { + context.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden); + } + else + { + bool isAccepted = false; + if (permissions.FirstOrDefault(p => p.Value == _claimsValue) != null) + isAccepted = true; + if (!isAccepted) + context.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden); + } + } +} + diff --git a/NetinaShop.Api/WebFramework/Bases/CrudController.cs b/NetinaShop.Api/WebFramework/Bases/CrudController.cs new file mode 100644 index 0000000..7aa009b --- /dev/null +++ b/NetinaShop.Api/WebFramework/Bases/CrudController.cs @@ -0,0 +1,218 @@ +namespace NetinaShop.Api.WebFramework.Bases; + + +public class CrudEndpoint where TEntity : ApiEntity, new() +{ + private readonly string _endpointName; + + public CrudEndpoint(string endpointName) + { + _endpointName = endpointName; + } + + public virtual void AddRoutes(IEndpointRouteBuilder app) + { + var group = app.NewVersionedApi(_endpointName).MapGroup($"api/{_endpointName}"); + + group.MapGet("", GetAllAsync) + .WithDisplayName("GetAll") + .HasApiVersion(1.0); + + group.MapGet("{id}", GetAsync) + .WithName("GetOne") + .HasApiVersion(1.0); + + group.MapPost("", Post) + .HasApiVersion(1.0); + + group.MapPut("", Put) + .HasApiVersion(1.0); + + group.MapDelete("", Delete) + .HasApiVersion(1.0); + } + + // GET:Get All Entity + public virtual async Task GetAllAsync(ISender sender , CancellationToken cancellationToken) + { + var res = sender.Send(Activator.CreateInstance(),cancellationToken); + return TypedResults.Ok(res); + } + + // GET:Get An Entity By Id + public async Task GetAsync(Guid id, ISender sender, CancellationToken cancellationToken) + => TypedResults.Ok(sender.Send(Activator.CreateInstance())); + + // POST:Create Entity + public virtual async Task Post([FromBody] TCreateCommand ent , ISender mediator , CancellationToken cancellationToken) + { + return TypedResults.Ok(await mediator.Send(ent, cancellationToken)); + } + + // PUT:Update Entity + public virtual async Task Put([FromBody] TEntity ent , IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken) + { + _repositoryWrapper.SetRepository().Update(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return TypedResults.Ok(); + } + + // DELETE:Delete Entity + public virtual async Task Delete(Guid id,IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); + _repositoryWrapper.SetRepository().Delete(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return TypedResults.Ok(); + } +} + +[ApiController] +//[AllowAnonymous] +[ApiResultFilter] +[Route("api/v{version:apiVersion}/[controller]")] // api/v1/[controller] +public class BaseController : ControllerBase +{ + //public UserRepository UserRepository { get; set; } => property injection + public bool UserIsAutheticated => HttpContext.User.Identity.IsAuthenticated; +} + +[Authorize(AuthenticationSchemes = "Bearer")] +public class CrudController : BaseController + where TDto : BaseDto, new() + where TEntity : ApiEntity, new() +{ + protected readonly IRepositoryWrapper _repositoryWrapper; + + public CrudController(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + + // GET:Get All Entity + [HttpGet] + public virtual async Task GetAllAsync(CancellationToken cancellationToken) + { + var projectTo = typeof(TDto).BaseType?.GetProperty("ProjectToDto")?.GetValue(null, null); + if (projectTo != null) + { + var exprss = projectTo as Expression>; + var entites = await _repositoryWrapper + .SetRepository() + .TableNoTracking + .Select(exprss) + .ToListAsync(cancellationToken); + return Ok(entites); + } + + throw new BaseApiException("ProjectTo Not Found"); + } + + // GET:Get An Entity By Id + [HttpGet("{id}")] + public virtual async Task GetAsync(string id, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); + var dto = ent.Adapt(); + return Ok(dto); + } + + // POST:Add New Entity + [HttpPost] + public virtual async Task PostOrginal([FromBody] TEntity ent, CancellationToken cancellationToken) + { + _repositoryWrapper.SetRepository().Add(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(ent); + } + + // POST:Add New Entity By Dto + [HttpPost("Dto")] + public async Task PostDto([FromBody] TDto dto, CancellationToken cancellationToken) + { + _repositoryWrapper + .SetRepository() + .Add(dto.ToEntity()); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(); + } + + // PUT:Update Entity + [HttpPut] + public virtual async Task Put([FromBody] TEntity ent, CancellationToken cancellationToken) + { + _repositoryWrapper + .SetRepository() + .Update(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(); + } + + // DELETE:Delete Entity + [HttpDelete] + [Route("{id:int}")] + public virtual async Task Delete(int id, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper + .SetRepository() + .GetByIdAsync(cancellationToken, id); + _repositoryWrapper.SetRepository().Delete(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(); + } +} + +[Authorize(AuthenticationSchemes = "Bearer")] +public class CrudController : BaseController + where TEntity : ApiEntity, new() +{ + protected readonly IRepositoryWrapper _repositoryWrapper; + + public CrudController(IRepositoryWrapper repositoryWrapper) + { + _repositoryWrapper = repositoryWrapper; + } + + // GET:Get All Entity + [HttpGet] + public virtual async Task GetAllAsync() + { + return Ok(await _repositoryWrapper.SetRepository().TableNoTracking.ToListAsync()); + } + + // GET:Get An Entity By Id + [HttpGet("{id}")] + public async Task GetAsync(int id, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); + return Ok(ent); + } + + // POST:Add New Entity + [HttpPost] + public virtual async Task Post([FromBody] TEntity ent, CancellationToken cancellationToken) + { + _repositoryWrapper.SetRepository().Add(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(ent); + } + + // PUT:Update Entity + [HttpPut] + public virtual async Task Put([FromBody] TEntity ent, CancellationToken cancellationToken) + { + _repositoryWrapper.SetRepository().Update(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(); + } + + // DELETE:Delete Entity + [HttpDelete("{id}")] + public virtual async Task Delete(int id, CancellationToken cancellationToken) + { + var ent = await _repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); + _repositoryWrapper.SetRepository().Delete(ent); + await _repositoryWrapper.SaveChangesAsync(cancellationToken); + return Ok(); + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Configurations/ConfigureJwtBearerOptions.cs b/NetinaShop.Api/WebFramework/Configurations/ConfigureJwtBearerOptions.cs new file mode 100644 index 0000000..77d98c1 --- /dev/null +++ b/NetinaShop.Api/WebFramework/Configurations/ConfigureJwtBearerOptions.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; + +namespace NetinaShop.Api.WebFramework.Configurations; + +public class ConfigureJwtBearerOptions : IPostConfigureOptions +{ + public void PostConfigure(string name, JwtBearerOptions options) + { + var originalOnMessageReceived = options.Events.OnMessageReceived; + options.Events.OnMessageReceived = async context => + { + await originalOnMessageReceived(context); + + if (string.IsNullOrEmpty(context.Token)) + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + + if (!string.IsNullOrEmpty(accessToken)) context.Token = accessToken; + } + }; + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Configurations/LoggerConfig.cs b/NetinaShop.Api/WebFramework/Configurations/LoggerConfig.cs new file mode 100644 index 0000000..d4a4959 --- /dev/null +++ b/NetinaShop.Api/WebFramework/Configurations/LoggerConfig.cs @@ -0,0 +1,19 @@ +namespace NetinaShop.Api.WebFramework.Configurations; + +public static class LoggerConfig +{ + public static void ConfigureSerilog() + { + var logName = $"{DirectoryAddress.Logs}/Log_Server_.log"; + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Console(theme: AnsiConsoleTheme.Literate) + .WriteTo.Sentry(o => + { + o.MinimumEventLevel = LogEventLevel.Error; + o.Dsn = "https://592b7fbb29464442a8e996247abe857f@watcher.igarson.app/7"; + }) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning) + .CreateLogger(); + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Configurations/PersianIdentityErrorDescriber.cs b/NetinaShop.Api/WebFramework/Configurations/PersianIdentityErrorDescriber.cs new file mode 100644 index 0000000..2485b95 --- /dev/null +++ b/NetinaShop.Api/WebFramework/Configurations/PersianIdentityErrorDescriber.cs @@ -0,0 +1,134 @@ +namespace NetinaShop.Api.WebFramework.Configurations; + +public class PersianIdentityErrorDescriber : IdentityErrorDescriber +{ + public override IdentityError DefaultError() + { + return new IdentityError { Code = nameof(DefaultError), Description = "ارور ناشناخته ای رخ داده است" }; + } + + public override IdentityError ConcurrencyFailure() + { + return new IdentityError + { Code = nameof(ConcurrencyFailure), Description = "در درخواست شما تداخلی ایجاد شده است" }; + } + + public override IdentityError PasswordMismatch() + { + return new IdentityError { Code = nameof(PasswordMismatch), Description = "رمز عبور اشتباه است" }; + } + + public override IdentityError InvalidToken() + { + return new IdentityError { Code = nameof(InvalidToken), Description = "توکن ارسالی اشتباه است" }; + } + + public override IdentityError LoginAlreadyAssociated() + { + return new IdentityError + { Code = nameof(LoginAlreadyAssociated), Description = "یوزری با این مشخصات در حال حاضر لاگین کرده است" }; + } + + public override IdentityError InvalidUserName(string userName) + { + return new IdentityError + { + Code = nameof(InvalidUserName), + Description = $"یوزر نیم '{userName}' صحیح نمی باشد فقط می توانید از حروف و اعداد استفاده کنید" + }; + } + + public override IdentityError InvalidEmail(string email) + { + return new IdentityError { Code = nameof(InvalidEmail), Description = $"ایمیل '{email}' صحیح نمی باشد" }; + } + + public override IdentityError DuplicateUserName(string userName) + { + return new IdentityError + { + Code = nameof(DuplicateUserName), + Description = $"یوزرنیم '{userName}' قبلا توسط اکانت دیگری استفاده شده است" + }; + } + + public override IdentityError DuplicateEmail(string email) + { + return new IdentityError + { Code = nameof(DuplicateEmail), Description = $"ایمیل '{email}' قبل استفاده شده است" }; + } + + public override IdentityError InvalidRoleName(string role) + { + return new IdentityError { Code = nameof(InvalidRoleName), Description = $"نقش '{role}' موجود نمی باشد" }; + } + + public override IdentityError DuplicateRoleName(string role) + { + return new IdentityError + { Code = nameof(DuplicateRoleName), Description = $"نقش '{role}' قبلا برای این کاربر استفاده شده است" }; + } + + public override IdentityError UserAlreadyHasPassword() + { + return new IdentityError + { Code = nameof(UserAlreadyHasPassword), Description = "کاربر قبلا رمز عبوری را استفاده کرده است" }; + } + + public override IdentityError UserLockoutNotEnabled() + { + return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "کاربر مورد نظر قفل شده است" }; + } + + public override IdentityError UserAlreadyInRole(string role) + { + return new IdentityError + { Code = nameof(UserAlreadyInRole), Description = "نشق مورد نظر برای این کاربر استفاده شده است" }; + } + + public override IdentityError UserNotInRole(string role) + { + return new IdentityError { Code = nameof(UserNotInRole), Description = $"کاربر مورد نظر در نقش '{role}' نیست" }; + } + + public override IdentityError PasswordTooShort(int length) + { + return new IdentityError + { Code = nameof(PasswordTooShort), Description = $"پسورد حداقل باید {length} کاراکتر باشد" }; + } + + public override IdentityError PasswordRequiresNonAlphanumeric() + { + return new IdentityError + { + Code = nameof(PasswordRequiresNonAlphanumeric), + Description = "رمز عبور باید حداقل یک کاراکتر غیر عددی داشته باشد" + }; + } + + public override IdentityError PasswordRequiresDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresDigit), Description = "پسور مورد نظر باید حداقل یک عدد داشته باشد ('0'-'9')" + }; + } + + public override IdentityError PasswordRequiresLower() + { + return new IdentityError + { + Code = nameof(PasswordRequiresLower), + Description = "پسورد مورد نظر باید حداقل یکی از حروف ('a'-'z') به صورت کوچک داشته باشد" + }; + } + + public override IdentityError PasswordRequiresUpper() + { + return new IdentityError + { + Code = nameof(PasswordRequiresUpper), + Description = "پسورد مورد نظر باید حداقل یکی از حروف ('A'-'Z') به صورت بزرگ داشته باشد" + }; + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Configurations/ServiceExtensions.cs b/NetinaShop.Api/WebFramework/Configurations/ServiceExtensions.cs new file mode 100644 index 0000000..08d2615 --- /dev/null +++ b/NetinaShop.Api/WebFramework/Configurations/ServiceExtensions.cs @@ -0,0 +1,180 @@ +namespace NetinaShop.Api.WebFramework.Configurations; + +public static class ServiceExtensions +{ + public static void AddIpRateLimit(this IServiceCollection services, IConfigurationRoot configuration) + { + + //load general configuration from appsettings.json + services.Configure(configuration.GetSection("IpRateLimiting")); + + //load ip rules from appsettings.json + services.Configure(configuration.GetSection("IpRateLimitPolicies")); + + // inject counter and rules stores + //services.AddInMemoryRateLimiting(); + services.AddSingleton(); + services.AddSingleton(); + services.AddDistributedRateLimiting(); + services.AddDistributedRateLimiting(); + services.AddRedisRateLimiting(); + + // configuration (resolvers, counter key builders) + services.AddSingleton(); + } + public static void AddCustomStackExchangeRedis(this IServiceCollection serviceCollection, SiteSettings siteSettings) + { + serviceCollection.AddStackExchangeRedisExtensions(options => + { + return new List + { + new() + { + Hosts = new[] + { + new RedisHost + { + Port = siteSettings.MasterRedisConfiguration.Port, + Host = siteSettings.MasterRedisConfiguration.Host + } + }, + Password = siteSettings.MasterRedisConfiguration.Password, + Ssl = false + } + }; + }); + } + + public static void AddCustomDbContext(this IServiceCollection serviceCollection, IConfigurationRoot Configuration) + { + serviceCollection.AddDbContextFactory(options => + { + options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + options.UseNpgsql(Configuration.GetConnectionString("Postgres"), b => b.MigrationsAssembly("NetinaShop.Repository")) + .UseProjectAssembly(typeof(ApplicationUser).Assembly); + //options.EnableServiceProviderCaching(true); + }).BuildServiceProvider(); + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + } + + public static void AddCustomResponseCompression(this IServiceCollection serviceCollection) + { + serviceCollection.Configure(options => + options.Level = CompressionLevel.Fastest); + serviceCollection.AddResponseCompression(options => + { + options.Providers.Add(); + options.EnableForHttps = true; + }); + } + + public static void AddCustomCores(this IServiceCollection serviceCollection) + { + serviceCollection.AddCors(options => options.AddPolicy("CorsPolicy", + builder => + { + builder.AllowAnyMethod() + .SetPreflightMaxAge(TimeSpan.FromHours(24)) + .WithExposedHeaders("Access-control-allow-origins") + .AllowAnyHeader() + .SetIsOriginAllowed(_ => true) + .AllowCredentials(); + })); + } + + public static void AddCustomController(this IServiceCollection serviceCollection) + { + serviceCollection.AddControllers(options => { options.Filters.Add(new AuthorizeFilter()); }) + .AddControllersAsServices() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + //options.SerializerSettings.ContractResolver = new DefaultContractResolver(); + } + ); + } + + public static void AddCustomMvc(this IServiceCollection serviceCollection) + { + serviceCollection + .AddMvc() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + }); + } + + public static void AddCustomAuthorization(this IServiceCollection serviceCollection) + { + serviceCollection.AddAuthorization(); + } + + public static void AddJwtCustomAuthentication(this IServiceCollection serviceCollection, JwtSettings jwtSettings) + { + serviceCollection.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + var secretKey = Encoding.UTF8.GetBytes(jwtSettings.SecretKey); + var validateParammetrs = new TokenValidationParameters + { + ClockSkew = TimeSpan.Zero, + RequireSignedTokens = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(secretKey), + RequireExpirationTime = true, + ValidateLifetime = true, + ValidateAudience = true, + ValidAudience = jwtSettings.Audience, + ValidateIssuer = true, + ValidIssuer = jwtSettings.Issuer + }; + options.RequireHttpsMetadata = true; + options.SaveToken = true; + options.TokenValidationParameters = validateParammetrs; + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + + // If the request is for our hub... + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken)) + // Read the token out of the query string + context.Token = accessToken.ToString(); + return Task.CompletedTask; + } + }; + }); + } + + public static void AddCustomIdentity(this IServiceCollection serviceCollection) + { + serviceCollection.AddIdentity(options => + { + options.Password.RequireLowercase = false; + options.Password.RequireUppercase = false; + options.Password.RequireDigit = false; + options.Password.RequireNonAlphanumeric = false; + options.User.RequireUniqueEmail = false; + }).AddEntityFrameworkStores() + .AddDefaultTokenProviders() + .AddErrorDescriber(); + ; + } + + public static void AddCustomApiVersioning(this IServiceCollection serviceCollection) + { + serviceCollection.AddApiVersioning(options => + { + options.AssumeDefaultVersionWhenUnspecified = true; + options.DefaultApiVersion = new ApiVersion(1, 0); + options.ApiVersionReader = new HeaderApiVersionReader("api-version"); + options.ReportApiVersions = true; + }); + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs b/NetinaShop.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs new file mode 100644 index 0000000..ac9f1ec --- /dev/null +++ b/NetinaShop.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs @@ -0,0 +1,207 @@ +namespace NetinaShop.Api.WebFramework.MiddleWares; + +public static class ExceptionHandlerMiddlewareExtensions +{ + public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder applicationBuilder) + { + return applicationBuilder.UseMiddleware(); + } +} + +public class ExceptionHandlerMiddleware +{ + private readonly IWebHostEnvironment _env; + private readonly ILogger _logger; + private readonly RequestDelegate _next; + + public ExceptionHandlerMiddleware( + RequestDelegate next, + IWebHostEnvironment env, + ILogger logger) + { + _next = next; + _env = env; + _logger = logger; + } + + public async Task Invoke(HttpContext context) + { + string message = null; + var httpStatusCode = HttpStatusCode.InternalServerError; + var apiStatusCode = ApiResultStatusCode.ServerError; + + try + { + await _next(context); + } + catch (BaseApiException exception) + { + _logger.LogError(exception, exception.Message); + httpStatusCode = exception.HttpStatusCode; + apiStatusCode = exception.ApiStatusCode; + + if (_env.IsDevelopment()) + { + var dic = new Dictionary + { + ["Exception"] = exception.Message, + ["StackTrace"] = exception.StackTrace + }; + if (exception.InnerException != null) + { + dic.Add("InnerException.Exception", exception.InnerException.Message); + dic.Add("InnerException.StackTrace", exception.InnerException.StackTrace); + } + + if (exception.AdditionalData != null) + dic.Add("AdditionalData", JsonConvert.SerializeObject(exception.AdditionalData)); + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + message = JsonConvert.SerializeObject(dic, new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }); + } + else + { + message = exception.Message; + } + if(exception.AdditionalData==null) + await WriteToResponseAsync(); + else + await WriteToResponseWithObjectAsync(exception.AdditionalData); + } + catch (SecurityTokenExpiredException exception) + { + _logger.LogError(exception, exception.Message); + SetUnAuthorizeResponse(exception); + await WriteToResponseAsync(); + } + catch (UnauthorizedAccessException exception) + { + _logger.LogError(exception, exception.Message); + SetUnAuthorizeResponse(exception); + await WriteToResponseAsync(); + } + + catch (Exception exception) + { + _logger.LogError(exception, exception.Message); + + if (_env.IsDevelopment()) + { + var dic = new Dictionary + { + ["Exception"] = exception.Message, + ["InnerException"] = exception.InnerException?.Message, + ["StackTrace"] = exception.StackTrace + }; + message = JsonConvert.SerializeObject(dic); + } + + message = exception.Message; + await WriteToResponseAsync(); + } + + async Task WriteToResponseAsync() + { + if (context.Response.HasStarted) + throw new InvalidOperationException("The response has already started, the http status code middleware will not be executed."); + + var result = new ApiResult(false, apiStatusCode, message); + + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }); + + context.Response.StatusCode = (int)httpStatusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(json); + } + + + + async Task WriteToResponseWithObjectAsync(object additionalData) + { + if (context.Response.HasStarted) + throw new InvalidOperationException( + "The response has already started, the http status code middleware will not be executed."); + + var result = new ApiResult(false, apiStatusCode, additionalData, message); + + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }); + + context.Response.StatusCode = (int)httpStatusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(json); + } + + void SetUnAuthorizeResponse(Exception exception) + { + httpStatusCode = HttpStatusCode.Unauthorized; + apiStatusCode = ApiResultStatusCode.UnAuthorized; + + if (_env.IsDevelopment()) + { + var dic = new Dictionary + { + ["Exception"] = exception.Message, + ["StackTrace"] = exception.StackTrace + }; + if (exception is SecurityTokenExpiredException tokenException) + dic.Add("Expires", tokenException.Expires.ToString()); + + message = JsonConvert.SerializeObject(dic); + } + } + + + JwtSecurityToken ReadJwtToken(bool fromHeader = true) + { + try + { + if (fromHeader) + { + var stream = context.Request.Headers.Values.First(v => v.FirstOrDefault().Contains("Bearer")) + .FirstOrDefault(); + var handler = new JwtSecurityTokenHandler(); + var jsonToken = handler.ReadToken(stream.Split(" ").Last()); + return jsonToken as JwtSecurityToken; + } + else + { + string stream = context.Request.Query["access_token"]; + ; + var handler = new JwtSecurityTokenHandler(); + var jsonToken = handler.ReadToken(stream.Split(" ").Last()); + return jsonToken as JwtSecurityToken; + } + } + catch (Exception e) + { + throw new BaseApiException(ApiResultStatusCode.UnAuthorized, e.Message + " Jwt is wrong", + HttpStatusCode.Unauthorized); + } + } + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/MiddleWares/PerformanceMiddleware.cs b/NetinaShop.Api/WebFramework/MiddleWares/PerformanceMiddleware.cs new file mode 100644 index 0000000..a854754 --- /dev/null +++ b/NetinaShop.Api/WebFramework/MiddleWares/PerformanceMiddleware.cs @@ -0,0 +1,35 @@ +namespace NetinaShop.Api.WebFramework.MiddleWares; + +public static class PerformanceMiddlewareExtensions +{ + public static IApplicationBuilder UsePerformanceMiddlewar(this IApplicationBuilder applicationBuilder) + { + return applicationBuilder.UseMiddleware(); + } +} + +public class PerformanceMiddleware +{ + private readonly ILogger _logger; + private readonly RequestDelegate _next; + private readonly Stopwatch _timer; + + public PerformanceMiddleware( + RequestDelegate next, + ILogger logger) + { + _next = next; + _logger = logger; + _timer = new Stopwatch(); + } + + public async System.Threading.Tasks.Task Invoke(HttpContext context) + { + _timer.Start(); + await _next(context); + _timer.Stop(); + + var elapsedMilliseconds = _timer.ElapsedMilliseconds; + _logger.LogWarning($"REQUEST TIMER : {elapsedMilliseconds}"); + } +} \ No newline at end of file diff --git a/NetinaShop.Api/WebFramework/Swagger/SwaggerConfiguration.cs b/NetinaShop.Api/WebFramework/Swagger/SwaggerConfiguration.cs new file mode 100644 index 0000000..a5b14ba --- /dev/null +++ b/NetinaShop.Api/WebFramework/Swagger/SwaggerConfiguration.cs @@ -0,0 +1,279 @@ +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OpenApi.Models; +using Pluralize.NET; +using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; + +namespace NetinaShop.Api.WebFramework.Swagger; + +public static class SwaggerConfiguration +{ + public static void AddCustomSwagger(this IServiceCollection services,string baseUrl) + { + services.AddSwaggerGen(options => + { + //var xmlDuc = Path.Combine(AppContext.BaseDirectory, "swaggerApi.xml"); + //options.IncludeXmlComments(xmlDuc,true); + options.SwaggerDoc("v1", + new OpenApiInfo + { + Version = "v1", + Title = "iGarson Api Dacument", + Description = "iGarson api for clients that wana use", + License = new OpenApiLicense { Name = "Vira Safir Fanavar " }, + Contact = new OpenApiContact + { + Name = "Amir Hossein Khademi", + Email = "avvampier@gmail.com", + Url = new Uri("http://amir-khademi.ir/") + } + }); + options.EnableAnnotations(); + options.DescribeAllParametersInCamelCase(); + options.IgnoreObsoleteActions(); + + //#region Versioning + + //// Remove version parameter from all Operations + //options.OperationFilter(); + + ////set version "api/v{version}/[controller]" from current swagger doc verion + //options.DocumentFilter(); + + ////Seperate and categorize end-points by doc version + //options.DocInclusionPredicate((version, desc) => + //{ + // if (!desc.TryGetMethodInfo(out var methodInfo)) return false; + // var versions = methodInfo.DeclaringType + // .GetCustomAttributes(true) + // .OfType() + // .SelectMany(attr => attr.Versions) + // .ToList(); + + // return versions.Any(v => $"v{v.ToString()}" == version); + //}); + + //#endregion + + #region Security + + var url = $"{baseUrl}/api/auth/login/swagger"; + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OAuth2, + Scheme = "Bearer", + Name = "Bearer", + Flows = new OpenApiOAuthFlows + { + Password = new OpenApiOAuthFlow + { + TokenUrl = new Uri(url) + } + } + }); + options.OperationFilter(true, "Bearer"); + + #endregion + + #region Customize + + options.OperationFilter(); + + #endregion + }); + } + + public static void UseCustomSwagger(this IApplicationBuilder app,string baseUrl) + { + app.UseSwagger(options => + { + options.SerializeAsV2 = true; + }); + + app.UseSwaggerUI(options => + { + options.InjectStylesheet("/assets/swagger-ui/x3/theme-flattop.css"); + options.DocExpansion(DocExpansion.None); + // Display + options.DefaultModelExpandDepth(2); + options.DefaultModelRendering(ModelRendering.Model); + options.DefaultModelsExpandDepth(-1); + options.DisplayOperationId(); + options.DisplayRequestDuration(); + options.EnableDeepLinking(); + options.EnableFilter(); + options.ShowExtensions(); + + options.OAuthUseBasicAuthenticationWithAccessCodeGrant(); + options.SwaggerEndpoint($"{baseUrl}/swagger/v1/swagger.json", "V1 Docs"); + }); + } +} + +public class RemoveVersionParameters : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + // Remove version parameter from all Operations + var versionParameter = operation.Parameters.SingleOrDefault(p => p.Name == "version"); + if (versionParameter != null) + operation.Parameters.Remove(versionParameter); + } +} + +public class SetVersionInPaths : IDocumentFilter +{ + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + if (swaggerDoc == null) + throw new ArgumentNullException(nameof(swaggerDoc)); + + var replacements = new OpenApiPaths(); + + foreach (var (key, value) in swaggerDoc.Paths) + replacements.Add(key.Replace("v{version}", swaggerDoc.Info.Version, StringComparison.InvariantCulture), + value); + + swaggerDoc.Paths = replacements; + } +} + +public class UnauthorizedResponsesOperationFilter : IOperationFilter +{ + private readonly bool includeUnauthorizedAndForbiddenResponses; + private readonly string schemeName; + + public UnauthorizedResponsesOperationFilter(bool includeUnauthorizedAndForbiddenResponses, + string schemeName = "Bearer") + { + this.includeUnauthorizedAndForbiddenResponses = includeUnauthorizedAndForbiddenResponses; + this.schemeName = schemeName; + } + + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var filters = context.ApiDescription.ActionDescriptor.FilterDescriptors; + + var hasAnynomousEndPoint = context.ApiDescription.ActionDescriptor.EndpointMetadata.Any(e => + e.GetType() == typeof(AllowAnonymousAttribute)); + + //var hasAnonymous = filters.Any(p => p.Filter is AllowAnonymousFilter); + if (hasAnynomousEndPoint) + return; + + /*var hasAuthorize = filters.Any(p => p.Filter is AuthorizeFilter); + if (!hasAuthorize) + return;*/ + + if (includeUnauthorizedAndForbiddenResponses) + { + operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); + operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); + } + + operation.Security.Add(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); + } +} + +public class ApplySummariesOperationFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var controllerActionDescriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor; + if (controllerActionDescriptor == null) return; + + var pluralizer = new Pluralizer(); + + var actionName = controllerActionDescriptor.ActionName; + var singularizeName = pluralizer.Singularize(controllerActionDescriptor.ControllerName); + var pluralizeName = pluralizer.Pluralize(singularizeName); + + var parameterCount = operation.Parameters.Where(p => p.Name != "version" && p.Name != "api-version").Count(); + + if (IsGetAllAction()) + { + if (!operation.Summary.HasValue()) + operation.Summary = $"Returns all {pluralizeName}"; + } + else if (IsActionName("Post", "Create")) + { + if (!operation.Summary.HasValue()) + operation.Summary = $"Creates a {singularizeName}"; + + if (operation.Parameters.Count > 0 && !operation.Parameters[0].Description.HasValue()) + operation.Parameters[0].Description = $"A {singularizeName} representation"; + } + else if (IsActionName("Read", "Get")) + { + if (!operation.Summary.HasValue()) + operation.Summary = $"Retrieves a {singularizeName} by unique id"; + + if (operation.Parameters.Count > 0 && !operation.Parameters[0].Description.HasValue()) + operation.Parameters[0].Description = $"a unique id for the {singularizeName}"; + } + else if (IsActionName("Put", "Edit", "Update")) + { + if (!operation.Summary.HasValue()) + operation.Summary = $"Updates a {singularizeName} by unique id"; + + //if (!operation.Parameters[0].OrderDescription.HasValue()) + // operation.Parameters[0].OrderDescription = $"A unique id for the {singularizeName}"; + + if (operation.Parameters.Count > 0 && !operation.Parameters[0].Description.HasValue()) + operation.Parameters[0].Description = $"A {singularizeName} representation"; + } + else if (IsActionName("Delete", "Remove")) + { + if (!operation.Summary.HasValue()) + operation.Summary = $"Deletes a {singularizeName} by unique id"; + + if (operation.Parameters.Count > 0 && !operation.Parameters[0].Description.HasValue()) + operation.Parameters[0].Description = $"A unique id for the {singularizeName}"; + } + else + { + if (!operation.Summary.HasValue()) + operation.Summary = $"{actionName} {pluralizeName}"; + } + + #region Local Functions + + bool IsGetAllAction() + { + foreach (var name in new[] { "Get", "Read", "Select" }) + if (actionName.Equals(name, StringComparison.OrdinalIgnoreCase) && parameterCount == 0 || + actionName.Equals($"{name}All", StringComparison.OrdinalIgnoreCase) || + actionName.Equals($"{name}{pluralizeName}", StringComparison.OrdinalIgnoreCase) || + actionName.Equals($"{name}All{singularizeName}", StringComparison.OrdinalIgnoreCase) || + actionName.Equals($"{name}All{pluralizeName}", StringComparison.OrdinalIgnoreCase)) + return true; + return false; + } + + bool IsActionName(params string[] names) + { + foreach (var name in names) + if (actionName.Contains(name, StringComparison.OrdinalIgnoreCase) || + actionName.Contains($"{name}ById", StringComparison.OrdinalIgnoreCase) || + actionName.Contains($"{name}{singularizeName}", StringComparison.OrdinalIgnoreCase) || + actionName.Contains($"{name}{singularizeName}ById", StringComparison.OrdinalIgnoreCase)) + return true; + return false; + } + + #endregion + } +} \ No newline at end of file diff --git a/NetinaShop.Common/CommonConfig.cs b/NetinaShop.Common/CommonConfig.cs new file mode 100644 index 0000000..63cf5e5 --- /dev/null +++ b/NetinaShop.Common/CommonConfig.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Common +{ + public class CommonConfig + { + + } +} diff --git a/NetinaShop.Common/Extensions/AssertExtensions.cs b/NetinaShop.Common/Extensions/AssertExtensions.cs new file mode 100644 index 0000000..936d96c --- /dev/null +++ b/NetinaShop.Common/Extensions/AssertExtensions.cs @@ -0,0 +1,30 @@ +using System.Collections; + +namespace NetinaShop.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/NetinaShop.Common/Extensions/ClassDisplayExtensions.cs b/NetinaShop.Common/Extensions/ClassDisplayExtensions.cs new file mode 100644 index 0000000..aa30ad7 --- /dev/null +++ b/NetinaShop.Common/Extensions/ClassDisplayExtensions.cs @@ -0,0 +1,39 @@ +using NetinaShop.Common.Models.Entity; + +namespace NetinaShop.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 ClassDisplay; + 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 ClassDisplay; + if (displayAttribute == null) + continue; + return displayAttribute.GetDescription(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/NetinaShop.Common/Extensions/DateTimeExtensions.cs b/NetinaShop.Common/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..7983510 --- /dev/null +++ b/NetinaShop.Common/Extensions/DateTimeExtensions.cs @@ -0,0 +1,59 @@ +namespace NetinaShop.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/NetinaShop.Common/Extensions/EnumExtensions.cs b/NetinaShop.Common/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..42c184e --- /dev/null +++ b/NetinaShop.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace NetinaShop.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/NetinaShop.Common/Extensions/NewtonJsonExtensions.cs b/NetinaShop.Common/Extensions/NewtonJsonExtensions.cs new file mode 100644 index 0000000..4b56fd6 --- /dev/null +++ b/NetinaShop.Common/Extensions/NewtonJsonExtensions.cs @@ -0,0 +1,15 @@ +namespace NetinaShop.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/NetinaShop.Common/Extensions/PhoneNumberExtensions.cs b/NetinaShop.Common/Extensions/PhoneNumberExtensions.cs new file mode 100644 index 0000000..1869bed --- /dev/null +++ b/NetinaShop.Common/Extensions/PhoneNumberExtensions.cs @@ -0,0 +1,41 @@ +using System.Text.RegularExpressions; + +namespace NetinaShop.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/NetinaShop.Common/Extensions/PropertyExtensions.cs b/NetinaShop.Common/Extensions/PropertyExtensions.cs new file mode 100644 index 0000000..1e70e2b --- /dev/null +++ b/NetinaShop.Common/Extensions/PropertyExtensions.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace NetinaShop.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/NetinaShop.Common/Extensions/RandomExtensions.cs b/NetinaShop.Common/Extensions/RandomExtensions.cs new file mode 100644 index 0000000..89e0a9e --- /dev/null +++ b/NetinaShop.Common/Extensions/RandomExtensions.cs @@ -0,0 +1,12 @@ +namespace NetinaShop.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/NetinaShop.Common/Extensions/StringExtensions.cs b/NetinaShop.Common/Extensions/StringExtensions.cs new file mode 100644 index 0000000..14d0b99 --- /dev/null +++ b/NetinaShop.Common/Extensions/StringExtensions.cs @@ -0,0 +1,165 @@ +namespace NetinaShop.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/NetinaShop.Common/Extensions/ValidationExtensions.cs b/NetinaShop.Common/Extensions/ValidationExtensions.cs new file mode 100644 index 0000000..268ad92 --- /dev/null +++ b/NetinaShop.Common/Extensions/ValidationExtensions.cs @@ -0,0 +1,21 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Api/AccessToken.cs b/NetinaShop.Common/Models/Api/AccessToken.cs new file mode 100644 index 0000000..a6e7ee7 --- /dev/null +++ b/NetinaShop.Common/Models/Api/AccessToken.cs @@ -0,0 +1,75 @@ +namespace NetinaShop.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 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/NetinaShop.Common/Models/Api/ApiResultStatusCode.cs b/NetinaShop.Common/Models/Api/ApiResultStatusCode.cs new file mode 100644 index 0000000..a4f187a --- /dev/null +++ b/NetinaShop.Common/Models/Api/ApiResultStatusCode.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; +using System.Net; + +namespace NetinaShop.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/NetinaShop.Common/Models/Api/AppSettings.cs b/NetinaShop.Common/Models/Api/AppSettings.cs new file mode 100644 index 0000000..15edc0b --- /dev/null +++ b/NetinaShop.Common/Models/Api/AppSettings.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Common.Models.Api +{ + public class AppSettings + { + public bool Seeded { get; set; } + } +} \ No newline at end of file diff --git a/NetinaShop.Common/Models/Api/FileUploadRequest.cs b/NetinaShop.Common/Models/Api/FileUploadRequest.cs new file mode 100644 index 0000000..54ec5de --- /dev/null +++ b/NetinaShop.Common/Models/Api/FileUploadRequest.cs @@ -0,0 +1,15 @@ +namespace NetinaShop.Common.Models.Api +{ + public enum FileUploadType + { + Handout, + Video, + Image + } + public class FileUploadRequest + { + public string StringBaseFile { get; set; } + public string FileName { get; set; } + public FileUploadType FileUploadType { get; set; } + } +} \ No newline at end of file diff --git a/NetinaShop.Common/Models/Api/HealthCheck.cs b/NetinaShop.Common/Models/Api/HealthCheck.cs new file mode 100644 index 0000000..bcb7369 --- /dev/null +++ b/NetinaShop.Common/Models/Api/HealthCheck.cs @@ -0,0 +1,12 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Api/ResponseFile.cs b/NetinaShop.Common/Models/Api/ResponseFile.cs new file mode 100644 index 0000000..f061fda --- /dev/null +++ b/NetinaShop.Common/Models/Api/ResponseFile.cs @@ -0,0 +1,9 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Api/TokenRequest.cs b/NetinaShop.Common/Models/Api/TokenRequest.cs new file mode 100644 index 0000000..323415f --- /dev/null +++ b/NetinaShop.Common/Models/Api/TokenRequest.cs @@ -0,0 +1,18 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Entity/ApiEntity.cs b/NetinaShop.Common/Models/Entity/ApiEntity.cs new file mode 100644 index 0000000..c43143f --- /dev/null +++ b/NetinaShop.Common/Models/Entity/ApiEntity.cs @@ -0,0 +1,48 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Entity/ClassDisplay.cs b/NetinaShop.Common/Models/Entity/ClassDisplay.cs new file mode 100644 index 0000000..ec02461 --- /dev/null +++ b/NetinaShop.Common/Models/Entity/ClassDisplay.cs @@ -0,0 +1,25 @@ +namespace NetinaShop.Common.Models.Entity +{ + [AttributeUsage(AttributeTargets.Class)] + public class ClassDisplay : Attribute + { + private readonly string _description; + private readonly string _name; + + public ClassDisplay(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/NetinaShop.Common/Models/Entity/IApiEntity.cs b/NetinaShop.Common/Models/Entity/IApiEntity.cs new file mode 100644 index 0000000..596b74e --- /dev/null +++ b/NetinaShop.Common/Models/Entity/IApiEntity.cs @@ -0,0 +1,13 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Exception/AppException.cs b/NetinaShop.Common/Models/Exception/AppException.cs new file mode 100644 index 0000000..d0ca3d3 --- /dev/null +++ b/NetinaShop.Common/Models/Exception/AppException.cs @@ -0,0 +1,26 @@ +using System.Runtime.Serialization; +using NetinaShop.Common.Models.Api; + +namespace NetinaShop.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/NetinaShop.Common/Models/Exception/BaseApiException.cs b/NetinaShop.Common/Models/Exception/BaseApiException.cs new file mode 100644 index 0000000..cf7a9cc --- /dev/null +++ b/NetinaShop.Common/Models/Exception/BaseApiException.cs @@ -0,0 +1,91 @@ +using System.Net; +using System.Runtime.Serialization; +using NetinaShop.Common.Models.Api; + +namespace NetinaShop.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/NetinaShop.Common/Models/IScopedDependency.cs b/NetinaShop.Common/Models/IScopedDependency.cs new file mode 100644 index 0000000..388114d --- /dev/null +++ b/NetinaShop.Common/Models/IScopedDependency.cs @@ -0,0 +1,6 @@ +namespace NetinaShop.Common.Models +{ + public interface IScopedDependency + { + } +} \ No newline at end of file diff --git a/NetinaShop.Common/Models/Mapper/BaseDto.cs b/NetinaShop.Common/Models/Mapper/BaseDto.cs new file mode 100644 index 0000000..cf3f69b --- /dev/null +++ b/NetinaShop.Common/Models/Mapper/BaseDto.cs @@ -0,0 +1,97 @@ +using System.ComponentModel; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using NetinaShop.Common.Models.Exception; + +namespace NetinaShop.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 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/NetinaShop.Common/Models/Mapper/IBaseDto.cs b/NetinaShop.Common/Models/Mapper/IBaseDto.cs new file mode 100644 index 0000000..902a3fd --- /dev/null +++ b/NetinaShop.Common/Models/Mapper/IBaseDto.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace NetinaShop.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/NetinaShop.Common/Models/Report/ChartUnit.cs b/NetinaShop.Common/Models/Report/ChartUnit.cs new file mode 100644 index 0000000..99f62b0 --- /dev/null +++ b/NetinaShop.Common/Models/Report/ChartUnit.cs @@ -0,0 +1,38 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Report/ReportRequest.cs b/NetinaShop.Common/Models/Report/ReportRequest.cs new file mode 100644 index 0000000..c82d2de --- /dev/null +++ b/NetinaShop.Common/Models/Report/ReportRequest.cs @@ -0,0 +1,24 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Report/ReportResult.cs b/NetinaShop.Common/Models/Report/ReportResult.cs new file mode 100644 index 0000000..e68bf36 --- /dev/null +++ b/NetinaShop.Common/Models/Report/ReportResult.cs @@ -0,0 +1,34 @@ +namespace NetinaShop.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/NetinaShop.Common/Models/Report/ReportableItem.cs b/NetinaShop.Common/Models/Report/ReportableItem.cs new file mode 100644 index 0000000..3203f84 --- /dev/null +++ b/NetinaShop.Common/Models/Report/ReportableItem.cs @@ -0,0 +1,89 @@ +using System.Collections; +using System.Reflection; + +namespace NetinaShop.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/NetinaShop.Common/NetinaShop.Common.csproj b/NetinaShop.Common/NetinaShop.Common.csproj new file mode 100644 index 0000000..eec09c7 --- /dev/null +++ b/NetinaShop.Common/NetinaShop.Common.csproj @@ -0,0 +1,40 @@ + + + + + + net5.0 + 10 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Core/Abstracts/ISmsService.cs b/NetinaShop.Core/Abstracts/ISmsService.cs new file mode 100644 index 0000000..e92d2e7 --- /dev/null +++ b/NetinaShop.Core/Abstracts/ISmsService.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.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/NetinaShop.Core/BaseServices/Abstracts/IJwtService.cs b/NetinaShop.Core/BaseServices/Abstracts/IJwtService.cs new file mode 100644 index 0000000..5a7499b --- /dev/null +++ b/NetinaShop.Core/BaseServices/Abstracts/IJwtService.cs @@ -0,0 +1,12 @@ +namespace NetinaShop.Core.BaseServices.Abstracts; + +public interface IJwtService : IScopedDependency +{ + Task> Generate(TUser user, Guid complexId, Guid roleId) where TUser : ApplicationUser; + Task> Generate(TUser user, Guid complexId) where TUser : ApplicationUser; + Task> Generate(TUser user) where TUser : ApplicationUser; + + Task> Generate(TUser user, Guid complexId, Guid roleId) where TUser : ApplicationUser; + Task> Generate(TUser user, Guid complexId) where TUser : ApplicationUser; + Task> Generate(TUser user) where TUser : ApplicationUser; +} \ No newline at end of file diff --git a/NetinaShop.Core/BaseServices/JwtService.cs b/NetinaShop.Core/BaseServices/JwtService.cs new file mode 100644 index 0000000..a77f311 --- /dev/null +++ b/NetinaShop.Core/BaseServices/JwtService.cs @@ -0,0 +1,151 @@ +namespace NetinaShop.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, Guid complexId, Guid roleId) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId, roleId.ToString()); + claims.Add(new Claim("ComplexId", complexId.ToString())); + + var token = BaseGenerate(user, claims); + token.Permissions = claims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList(); + return token; + } + public async Task> Generate(TUser user, Guid complexId) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId); + claims.Add(new Claim("ComplexId", complexId.ToString())); + + return BaseGenerate(user, claims); + } + 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, Guid complexId, Guid roleId) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId, roleId.ToString()); + claims.Add(new Claim("ComplexId", complexId.ToString())); + + var token = BaseGenerate(user, claims); + token.Permissions = claims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList(); + return token; + } + + + public async Task> Generate(TUser user, Guid complexId) where TUser : ApplicationUser + { + var tokenId = StringExtensions.GetId(8); + var claims = await GetClaims(user, tokenId); + claims.Add(new Claim("ComplexId", complexId.ToString())); + + return BaseGenerate(user, claims); + } + 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, string roleId) where TUser : ApplicationUser + { + var applicationRole = await _roleManager.FindByIdAsync(roleId); + var roleClaims = await _roleManager.GetClaimsAsync(applicationRole); + var claims = new List(); + 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())); + claims.Add(new Claim(ClaimTypes.Role, applicationRole.EnglishName)); + claims.Add(new Claim("RoleId", applicationRole.Id.ToString())); + if (baseUser.Email != null) + claims.Add(new Claim(ClaimTypes.Email, baseUser.Email)); + claims.AddRange(roleClaims); + 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/NetinaShop.Core/CoreConfig.cs b/NetinaShop.Core/CoreConfig.cs new file mode 100644 index 0000000..3e61f34 --- /dev/null +++ b/NetinaShop.Core/CoreConfig.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Core +{ + public class CoreConfig + { + + } +} diff --git a/NetinaShop.Core/CoreServices/Abstracts/IAccountService.cs b/NetinaShop.Core/CoreServices/Abstracts/IAccountService.cs new file mode 100644 index 0000000..abeaee6 --- /dev/null +++ b/NetinaShop.Core/CoreServices/Abstracts/IAccountService.cs @@ -0,0 +1,11 @@ +namespace NetinaShop.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/NetinaShop.Core/CoreServices/AccountService.cs b/NetinaShop.Core/CoreServices/AccountService.cs new file mode 100644 index 0000000..f5d1346 --- /dev/null +++ b/NetinaShop.Core/CoreServices/AccountService.cs @@ -0,0 +1,156 @@ +namespace NetinaShop.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; + jwt = await _jwtService.Generate(user); + + + return jwt; + } + + + +} \ No newline at end of file diff --git a/NetinaShop.Core/EntityServices/Abstracts/IUserService.cs b/NetinaShop.Core/EntityServices/Abstracts/IUserService.cs new file mode 100644 index 0000000..6b246d7 --- /dev/null +++ b/NetinaShop.Core/EntityServices/Abstracts/IUserService.cs @@ -0,0 +1,21 @@ +namespace NetinaShop.Core.EntityServices.Abstracts; + +public interface IUserService : IScopedDependency +{ + Task GetUserProfileAsync(CancellationToken cancellationToken); + Task> GetUsersAsync(int page = 0, CancellationToken cancellationToken = default); + Task GetUserAsync(Guid userId); + Task CreateUserAsync(string phoneNumber); + 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 GetRoleAsync(Guid roleId); + Task CreateRoleAsync(RoleActionRequestDto request); + Task EditRoleAsync(RoleActionRequestDto request); + Task RemoveRoleAsync(Guid roleId); + List GetPermissions(); +} \ No newline at end of file diff --git a/NetinaShop.Core/EntityServices/UserService.cs b/NetinaShop.Core/EntityServices/UserService.cs new file mode 100644 index 0000000..e6b12f6 --- /dev/null +++ b/NetinaShop.Core/EntityServices/UserService.cs @@ -0,0 +1,314 @@ +namespace NetinaShop.Core.EntityServices; + + +public class UserService : IUserService +{ + private readonly ICurrentUserService _currentUserService; + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + private readonly IRepositoryWrapper _repositoryWrapper; + private readonly IJwtService _jwtService; + + public UserService(ICurrentUserService currentUserService, + UserManager userManager, + RoleManager roleManager, + IRepositoryWrapper repositoryWrapper, + IJwtService jwtService) + { + _currentUserService = currentUserService; + _userManager = userManager; + _roleManager = roleManager; + _repositoryWrapper = repositoryWrapper; + _jwtService = jwtService; + } + + + 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, CancellationToken cancellationToken = default) + { + var users = await _userManager.Users.Skip(page * 15).Take(15).Select(ApplicationUserMapper.ProjectToSDto).ToListAsync(cancellationToken); + + return users; + } + + public async Task GetUserAsync(Guid userId) + { + 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) + { + 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))); + } + } + 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))); + } + + 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 + .Skip(page * 15) + .Take(15) + .ToListAsync(cancellationToken); + return roles; + } + + public async Task GetRoleAsync(Guid roleId) + { + 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) + { + 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) + { + 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()) + { + 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)); + + foreach (var claim in roleClaims) + await _roleManager.RemoveClaimAsync(applicationRole, claim); + + return true; + } + + public async Task RemoveRoleAsync(Guid roleId) + { + 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/NetinaShop.Core/Models/Api/ApiResult.cs b/NetinaShop.Core/Models/Api/ApiResult.cs new file mode 100644 index 0000000..ad8a1b2 --- /dev/null +++ b/NetinaShop.Core/Models/Api/ApiResult.cs @@ -0,0 +1,126 @@ +namespace NetinaShop.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/NetinaShop.Core/NetinaShop.Core.csproj b/NetinaShop.Core/NetinaShop.Core.csproj new file mode 100644 index 0000000..a83ffd2 --- /dev/null +++ b/NetinaShop.Core/NetinaShop.Core.csproj @@ -0,0 +1,63 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Domain/DomainConfig.cs b/NetinaShop.Domain/DomainConfig.cs new file mode 100644 index 0000000..5a3acad --- /dev/null +++ b/NetinaShop.Domain/DomainConfig.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain +{ + public class DomainConfig + { + + } +} diff --git a/NetinaShop.Domain/Dtos/RequestDtos/LoginRequestDto.cs b/NetinaShop.Domain/Dtos/RequestDtos/LoginRequestDto.cs new file mode 100644 index 0000000..4e3b56e --- /dev/null +++ b/NetinaShop.Domain/Dtos/RequestDtos/LoginRequestDto.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.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/NetinaShop.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs b/NetinaShop.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs new file mode 100644 index 0000000..a809ea1 --- /dev/null +++ b/NetinaShop.Domain/Dtos/RequestDtos/RoleActionRequestDto.cs @@ -0,0 +1,10 @@ +namespace NetinaShop.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/NetinaShop.Domain/Dtos/RequestDtos/SignUpRequestDto.cs b/NetinaShop.Domain/Dtos/RequestDtos/SignUpRequestDto.cs new file mode 100644 index 0000000..74fe012 --- /dev/null +++ b/NetinaShop.Domain/Dtos/RequestDtos/SignUpRequestDto.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.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/NetinaShop.Domain/Dtos/RequestDtos/UserActionRequestDto.cs b/NetinaShop.Domain/Dtos/RequestDtos/UserActionRequestDto.cs new file mode 100644 index 0000000..90c96b1 --- /dev/null +++ b/NetinaShop.Domain/Dtos/RequestDtos/UserActionRequestDto.cs @@ -0,0 +1,14 @@ +namespace NetinaShop.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/NetinaShop.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs b/NetinaShop.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs new file mode 100644 index 0000000..90a03e4 --- /dev/null +++ b/NetinaShop.Domain/Dtos/ResponseDtos/ProfileResponseDto.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.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/NetinaShop.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs b/NetinaShop.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs new file mode 100644 index 0000000..52fd4ea --- /dev/null +++ b/NetinaShop.Domain/Dtos/ResponseDtos/VerifyCodeResponseDto.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Dtos.ResponseDtos; + +public class VerifyCodeResponseDto +{ + public SignUpStatus SignUpStatus { get; set; } + +} \ No newline at end of file diff --git a/NetinaShop.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs b/NetinaShop.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs new file mode 100644 index 0000000..6fbf0c8 --- /dev/null +++ b/NetinaShop.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs @@ -0,0 +1,16 @@ +namespace NetinaShop.Domain.Dtos.SmallDtos; + +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 List RoleIds { get; set; } = new(); + public long BirthDateTimeStamp => DateTimeExtensions.DateTimeToUnixTimeStamp(BirthDate); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Blogs/Blog.cs b/NetinaShop.Domain/Entities/Blogs/Blog.cs new file mode 100644 index 0000000..23aa769 --- /dev/null +++ b/NetinaShop.Domain/Entities/Blogs/Blog.cs @@ -0,0 +1,15 @@ +namespace NetinaShop.Domain.Entities.Blogs; + +public class Blog : ApiEntity +{ + 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/NetinaShop.Domain/Entities/Blogs/BlogCategory.cs b/NetinaShop.Domain/Entities/Blogs/BlogCategory.cs new file mode 100644 index 0000000..47ca3e7 --- /dev/null +++ b/NetinaShop.Domain/Entities/Blogs/BlogCategory.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.Domain.Entities.Blogs; + +public class BlogCategory : ApiEntity +{ + 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/NetinaShop.Domain/Entities/Blogs/BlogStorageFile.cs b/NetinaShop.Domain/Entities/Blogs/BlogStorageFile.cs new file mode 100644 index 0000000..9e19bf8 --- /dev/null +++ b/NetinaShop.Domain/Entities/Blogs/BlogStorageFile.cs @@ -0,0 +1,9 @@ +using NetinaShop.Domain.Entities.StorageFiles; + +namespace NetinaShop.Domain.Entities.Blogs; + +public class BlogStorageFile : StorageFile +{ + public Guid BlogId { get; internal set; } + public Blog? Blog { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Brands/Brand.cs b/NetinaShop.Domain/Entities/Brands/Brand.cs new file mode 100644 index 0000000..2dd37f1 --- /dev/null +++ b/NetinaShop.Domain/Entities/Brands/Brand.cs @@ -0,0 +1,13 @@ +using NetinaShop.Domain.Entities.Products; + +namespace NetinaShop.Domain.Entities.Brands; + +public class Brand : ApiEntity +{ + public string Name { get; internal set; } = string.Empty; + public string Description { get; internal set; } = string.Empty; + public bool HasSpecialPage { get; internal set; } + public string PageUrl { get; internal set; } = string.Empty; + public List Products { get; internal set; } = new(); + public List Files { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Brands/BrandStorageFile.cs b/NetinaShop.Domain/Entities/Brands/BrandStorageFile.cs new file mode 100644 index 0000000..d041a13 --- /dev/null +++ b/NetinaShop.Domain/Entities/Brands/BrandStorageFile.cs @@ -0,0 +1,9 @@ +using NetinaShop.Domain.Entities.StorageFiles; + +namespace NetinaShop.Domain.Entities.Brands; + +public class BrandStorageFile : StorageFile +{ + public Guid BrandId { get; internal set; } + public Brand? Brand { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Categories/Category.cs b/NetinaShop.Domain/Entities/Categories/Category.cs new file mode 100644 index 0000000..e608ea6 --- /dev/null +++ b/NetinaShop.Domain/Entities/Categories/Category.cs @@ -0,0 +1,16 @@ +using NetinaShop.Domain.Entities.Products; + +namespace NetinaShop.Domain.Entities.Categories; + +public class Category : ApiEntity +{ + public string Name { get; internal set; } = string.Empty; + public string Description { get; internal set; } = string.Empty; + + public Guid ParentId { get; internal set; } + public Category? Parent { get; internal set; } + + public List Children { get; internal set; } = new(); + public List Products { get; internal set; } = new(); + public List Files { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Categories/CategoryStorageFile.cs b/NetinaShop.Domain/Entities/Categories/CategoryStorageFile.cs new file mode 100644 index 0000000..3b890cf --- /dev/null +++ b/NetinaShop.Domain/Entities/Categories/CategoryStorageFile.cs @@ -0,0 +1,9 @@ +using NetinaShop.Domain.Entities.StorageFiles; + +namespace NetinaShop.Domain.Entities.Categories; + +public class CategoryStorageFile : StorageFile +{ + public Guid CategoryId { get; internal set; } + public Category? Category { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Discounts/CategoryDiscount.cs b/NetinaShop.Domain/Entities/Discounts/CategoryDiscount.cs new file mode 100644 index 0000000..39ab22c --- /dev/null +++ b/NetinaShop.Domain/Entities/Discounts/CategoryDiscount.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Entities.Discounts; + +public class CategoryDiscount : Discount +{ + public Guid CategoryId { get; internal set; } + public Category? Category { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Discounts/Discount.cs b/NetinaShop.Domain/Entities/Discounts/Discount.cs new file mode 100644 index 0000000..db83eda --- /dev/null +++ b/NetinaShop.Domain/Entities/Discounts/Discount.cs @@ -0,0 +1,23 @@ +namespace NetinaShop.Domain.Entities.Discounts; + +public class Discount : ApiEntity +{ + public string Code { get; internal set; } = string.Empty; + public int DiscountPercent { get; internal set; } + public long DiscountAmount { get; internal set; } + public bool HasCode { get; internal set; } + public DiscountAmountType AmountType { get; internal set; } + public DiscountType Type { get; internal set; } + public int Count { get; internal set; } + public DateTime StartDate { get; internal set; } + public DateTime ExpireDate { get; internal set; } + public long PriceFloor { get; internal set; } + public bool HasPriceFloor { get; internal set; } + public long PriceCeiling { get; internal set; } + public bool HasPriceCeiling { get; internal set; } + public bool IsInfinity { get; internal set; } + public long UseCount { get; internal set; } + public bool IsForInvitation { get; internal set; } + + public List Orders { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Discounts/ProductDiscount.cs b/NetinaShop.Domain/Entities/Discounts/ProductDiscount.cs new file mode 100644 index 0000000..e679e34 --- /dev/null +++ b/NetinaShop.Domain/Entities/Discounts/ProductDiscount.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Entities.Discounts; + +public class ProductDiscount : Discount +{ + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Orders/Order.cs b/NetinaShop.Domain/Entities/Orders/Order.cs new file mode 100644 index 0000000..155d0d4 --- /dev/null +++ b/NetinaShop.Domain/Entities/Orders/Order.cs @@ -0,0 +1,20 @@ +namespace NetinaShop.Domain.Entities.Orders; + +public class Order : ApiEntity +{ + public long TotalPrice { get; internal set; } + public long DeliveryPrice { get; internal set; } + public long TaxesPrice { get; internal set; } + public long ServicePrice { get; internal set; } + public long PackingPrice { get; internal set; } + public long TotalProductsPrice { get; internal set; } + public long DiscountPrice { get; internal set; } + public bool IsPayed { get; internal set; } + public OrderStatus OrderStatus { get; internal set; } + public DateTime DoneAt { get; internal set; } + public DateTime OrderAt { get; internal set; } + public int PreparingMinute { get; internal set; } + public string DiscountCode { get; internal set; } = string.Empty; + + public List OrderProducts { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Orders/OrderDelivery.cs b/NetinaShop.Domain/Entities/Orders/OrderDelivery.cs new file mode 100644 index 0000000..ccb562f --- /dev/null +++ b/NetinaShop.Domain/Entities/Orders/OrderDelivery.cs @@ -0,0 +1,11 @@ +namespace NetinaShop.Domain.Entities.Orders; + +public class OrderDelivery : ApiEntity +{ + public string Address { get; internal set; } = string.Empty; + public string PostalCode { get; internal set; } = string.Empty; + public string ReceiverPhoneNumber { get; internal set; } = string.Empty; + public string ReceiverFullName { get; internal set; } = string.Empty; + public Guid OrderId { get; internal set; } + public Order? Order { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Orders/OrderProduct.cs b/NetinaShop.Domain/Entities/Orders/OrderProduct.cs new file mode 100644 index 0000000..eb9cef5 --- /dev/null +++ b/NetinaShop.Domain/Entities/Orders/OrderProduct.cs @@ -0,0 +1,17 @@ +using NetinaShop.Domain.Entities.Products; + +namespace NetinaShop.Domain.Entities.Orders; + +public class OrderProduct : ApiEntity +{ + public int Count { get; internal set; } + public float ProductFee { get; internal set; } + public float ProductCost { get; internal set; } + public OrderStatus OrderProductStatus { get; internal set; } + + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } + + public Guid OrderId { get; internal set; } + public Order? Order { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Products/Product.cs b/NetinaShop.Domain/Entities/Products/Product.cs new file mode 100644 index 0000000..c3c5678 --- /dev/null +++ b/NetinaShop.Domain/Entities/Products/Product.cs @@ -0,0 +1,23 @@ +using NetinaShop.Domain.Entities.Brands; +using NetinaShop.Domain.Entities.Orders; + +namespace NetinaShop.Domain.Entities.Products; + +public class Product : ApiEntity +{ + public string PersianName { get; internal set; } = string.Empty; + public string EnglishName { get; internal set; } = string.Empty; + public string Summery { get; internal set; } = string.Empty; + public string ExpertCheck { get; internal set; } = string.Empty; + public string Tags { get; internal set; } = string.Empty; + public string Warranty { get; set; } = string.Empty; + + public Guid BrandId { get; internal set; } + public Brand? Brand { get; set; } + + public List Specifications { get; internal set; } = new(); + public List Reviews { get; internal set; } = new(); + public List Categories { get; internal set; } = new(); + public List Files { get; internal set; } = new(); + public List OrderProducts { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Products/ProductCategory.cs b/NetinaShop.Domain/Entities/Products/ProductCategory.cs new file mode 100644 index 0000000..d89b994 --- /dev/null +++ b/NetinaShop.Domain/Entities/Products/ProductCategory.cs @@ -0,0 +1,11 @@ +using NetinaShop.Domain.Entities.Categories; + +namespace NetinaShop.Domain.Entities.Products; + +public class ProductCategory : ApiEntity +{ + public Guid CategoryId { get; internal set; } + public Category? Category { get; internal set; } + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Products/ProductStorageFile.cs b/NetinaShop.Domain/Entities/Products/ProductStorageFile.cs new file mode 100644 index 0000000..d8476a7 --- /dev/null +++ b/NetinaShop.Domain/Entities/Products/ProductStorageFile.cs @@ -0,0 +1,9 @@ +using NetinaShop.Domain.Entities.StorageFiles; + +namespace NetinaShop.Domain.Entities.Products; + +public class ProductStorageFile : StorageFile +{ + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Products/Review.cs b/NetinaShop.Domain/Entities/Products/Review.cs new file mode 100644 index 0000000..caf8cb4 --- /dev/null +++ b/NetinaShop.Domain/Entities/Products/Review.cs @@ -0,0 +1,17 @@ +using NetinaShop.Domain.Entities.Users; + +namespace NetinaShop.Domain.Entities.Products; + +public class Review : ApiEntity +{ + public string Title { get; internal set; } = string.Empty; + public string Comment { get; internal set; } = string.Empty; + public float Rate { get; internal set; } + public bool IsBuyer { get; internal set; } + + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } + + public Guid UserId { get; internal set; } + public ApplicationUser? User { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Products/Specification.cs b/NetinaShop.Domain/Entities/Products/Specification.cs new file mode 100644 index 0000000..0bc8881 --- /dev/null +++ b/NetinaShop.Domain/Entities/Products/Specification.cs @@ -0,0 +1,16 @@ +namespace NetinaShop.Domain.Entities.Products; + +public class Specification : ApiEntity +{ + public string Title { get; internal set; } = string.Empty; + public string Detail { get; internal set; } = string.Empty; + public bool IsFeature { get; internal set; } + + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } + + public Guid ParentId { get; internal set; } + public Specification? Parent { get; internal set; } + + public List Children { get; internal set; } = new(); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/StorageFiles/StorageFile.cs b/NetinaShop.Domain/Entities/StorageFiles/StorageFile.cs new file mode 100644 index 0000000..0f29568 --- /dev/null +++ b/NetinaShop.Domain/Entities/StorageFiles/StorageFile.cs @@ -0,0 +1,11 @@ +namespace NetinaShop.Domain.Entities.StorageFiles; + +public class StorageFile : ApiEntity +{ + 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/NetinaShop.Domain/Entities/Users/ApplicationRole.cs b/NetinaShop.Domain/Entities/Users/ApplicationRole.cs new file mode 100644 index 0000000..43c26b5 --- /dev/null +++ b/NetinaShop.Domain/Entities/Users/ApplicationRole.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.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/NetinaShop.Domain/Entities/Users/ApplicationUser.cs b/NetinaShop.Domain/Entities/Users/ApplicationUser.cs new file mode 100644 index 0000000..9dde1b4 --- /dev/null +++ b/NetinaShop.Domain/Entities/Users/ApplicationUser.cs @@ -0,0 +1,18 @@ +namespace NetinaShop.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 DateTime BirthDate { get; set; } + public Gender Gender { get; set; } + public SignUpStatus SignUpStatus { get; set; } + + public List Addresses { get; set; } = new(); +} diff --git a/NetinaShop.Domain/Entities/Users/UserAddress.cs b/NetinaShop.Domain/Entities/Users/UserAddress.cs new file mode 100644 index 0000000..84d8010 --- /dev/null +++ b/NetinaShop.Domain/Entities/Users/UserAddress.cs @@ -0,0 +1,14 @@ +namespace NetinaShop.Domain.Entities.Users; + +public class UserAddress : ApiEntity +{ + public string Address { get; internal set; } = string.Empty; + public string PostalCode { get; internal set; } = string.Empty; + public string ReceiverFullName { get; internal set; } = string.Empty; + public string ReceiverPhoneNumber { get; internal set; } = string.Empty; + public float LocationLat { get; internal set; } + public float LocationLong { get; internal set; } + + public Guid UserId { get; internal set; } + public ApplicationUser? User { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Users/UserFavoriteProduct.cs b/NetinaShop.Domain/Entities/Users/UserFavoriteProduct.cs new file mode 100644 index 0000000..86b6d14 --- /dev/null +++ b/NetinaShop.Domain/Entities/Users/UserFavoriteProduct.cs @@ -0,0 +1,9 @@ +namespace NetinaShop.Domain.Entities.Users; + +public class UserFavoriteProduct : ApiEntity +{ + public Guid ProductId { get; internal set; } + public Product? Product { get; internal set; } + public Guid UserId { get; internal set; } + public ApplicationUser? User { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Entities/Warehouses/Shipping.cs b/NetinaShop.Domain/Entities/Warehouses/Shipping.cs new file mode 100644 index 0000000..3c15f39 --- /dev/null +++ b/NetinaShop.Domain/Entities/Warehouses/Shipping.cs @@ -0,0 +1,10 @@ +namespace NetinaShop.Domain.Entities.Warehouses; + +public class Shipping : ApiEntity +{ + public string Title { get; internal set; } = string.Empty; + public string WarehouseName { get; internal set; } = string.Empty; + public bool IsFastShipping { get; internal set; } + public bool IsShipBySeller { get; internal set; } + public bool IsOriginalWarehouse { get; internal set; } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/DiscountAmountType.cs b/NetinaShop.Domain/Enums/DiscountAmountType.cs new file mode 100644 index 0000000..0ae2743 --- /dev/null +++ b/NetinaShop.Domain/Enums/DiscountAmountType.cs @@ -0,0 +1,9 @@ +namespace NetinaShop.Domain.Enums; + +public enum DiscountAmountType +{ + [Display(Name = "درصدی")] + Percent, + [Display(Name = "مبلغی")] + Amount +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/DiscountType.cs b/NetinaShop.Domain/Enums/DiscountType.cs new file mode 100644 index 0000000..e8c1765 --- /dev/null +++ b/NetinaShop.Domain/Enums/DiscountType.cs @@ -0,0 +1,13 @@ +namespace NetinaShop.Domain.Enums; + +public enum DiscountType +{ + [Display(Name = "همه اقلام")] + All, + [Display(Name = "محصولی")] + Product, + [Display(Name = "دسته ای")] + Category, + [Display(Name = "مشترکی")] + Subscriber +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/Gender.cs b/NetinaShop.Domain/Enums/Gender.cs new file mode 100644 index 0000000..a7ed7d6 --- /dev/null +++ b/NetinaShop.Domain/Enums/Gender.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Enums; + +public enum Gender +{ + Male, + Female +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/OrderStatus.cs b/NetinaShop.Domain/Enums/OrderStatus.cs new file mode 100644 index 0000000..a991d57 --- /dev/null +++ b/NetinaShop.Domain/Enums/OrderStatus.cs @@ -0,0 +1,22 @@ +namespace NetinaShop.Domain.Enums; + +public enum OrderStatus +{ + [Display(Name = "سبد خرید",Description = "ORDERBAG")] + OrderBag = 0, + + [Display(Name = "ثبت شده", Description = "SUBMITTED")] + Submitted = 1, + + [Display(Name = "پرداخت شده", Description = "PAID")] + Paid = 2, + + [Display(Name = "ارسال شده", Description = "DELIVERED")] + Delivered = 3, + + [Display(Name = "انجام شده", Description = "DONE")] + Done = 4, + + [Display(Name = "کنسل شده", Description = "CANCELED")] + Canceled = 5 +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/SignUpStatus.cs b/NetinaShop.Domain/Enums/SignUpStatus.cs new file mode 100644 index 0000000..ca84dc8 --- /dev/null +++ b/NetinaShop.Domain/Enums/SignUpStatus.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.Domain.Enums; + +public enum SignUpStatus +{ + StartSignOn = 0, + PhoneNumberVerified = 1, + SignUpCompleted = 10, +} \ No newline at end of file diff --git a/NetinaShop.Domain/Enums/StorageFileType.cs b/NetinaShop.Domain/Enums/StorageFileType.cs new file mode 100644 index 0000000..5377304 --- /dev/null +++ b/NetinaShop.Domain/Enums/StorageFileType.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Enums; + +public enum StorageFileType +{ + Image, + Video +} \ No newline at end of file diff --git a/NetinaShop.Domain/FodyWeavers.xml b/NetinaShop.Domain/FodyWeavers.xml new file mode 100644 index 0000000..d5abfed --- /dev/null +++ b/NetinaShop.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/NetinaShop.Domain/Mappers/ApplicationUserMapper.g.cs b/NetinaShop.Domain/Mappers/ApplicationUserMapper.g.cs new file mode 100644 index 0000000..48e1b84 --- /dev/null +++ b/NetinaShop.Domain/Mappers/ApplicationUserMapper.g.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq.Expressions; +using NetinaShop.Domain.Dtos.SmallDtos; +using NetinaShop.Domain.Entities.Users; + +namespace NetinaShop.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, + 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.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, + 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.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, + Id = p7.Id + }; + } +} \ No newline at end of file diff --git a/NetinaShop.Domain/MappingRegister.cs b/NetinaShop.Domain/MappingRegister.cs new file mode 100644 index 0000000..fe1f588 --- /dev/null +++ b/NetinaShop.Domain/MappingRegister.cs @@ -0,0 +1,11 @@ +namespace NetinaShop.Domain; + +public class MappingRegister : ICodeGenerationRegister +{ + public void Register(CodeGenerationConfig config) + { + //config.AdaptTwoWays("[name]SDto", MapType.Map | MapType.MapToTarget | MapType.Projection) + // .AlterType(); + } + +} \ No newline at end of file diff --git a/NetinaShop.Domain/Models/Claims/ApplicationClaims.cs b/NetinaShop.Domain/Models/Claims/ApplicationClaims.cs new file mode 100644 index 0000000..62d1727 --- /dev/null +++ b/NetinaShop.Domain/Models/Claims/ApplicationClaims.cs @@ -0,0 +1,221 @@ +using System.Security.Claims; + +namespace NetinaShop.Domain.Models.Claims; + +public static class ApplicationClaims +{ + + public static ClaimDto ManageBlogs { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageBlogs, + }; + public static ClaimDto ViewBlogs { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewBlogs, + }; + + public static ClaimDto ManageBrands { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageBrands, + }; + public static ClaimDto ViewBrands { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewBrands, + }; + + public static ClaimDto ManageCategories { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageCategories, + }; + public static ClaimDto ViewCategories { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewCategories, + }; + + public static ClaimDto ManageDiscounts { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageDiscounts, + }; + public static ClaimDto ViewDiscounts { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewDiscounts, + }; + + public static ClaimDto ManageOrders { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageOrders, + }; + public static ClaimDto ViewAllOrders { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewAllOrders, + }; + public static ClaimDto ViewMineOrders { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewMineOrders, + }; + public static ClaimDto CreateOrder { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.CreateOrder, + }; + + public static ClaimDto ManageProducts { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageProducts, + }; + public static ClaimDto ViewProducts { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewProducts, + }; + + public static ClaimDto ManageReview { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageReview, + }; + public static ClaimDto ViewAllReviews { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewAllReviews, + }; + public static ClaimDto ViewMineReviews { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewMineReviews, + }; + public static ClaimDto AddReview { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.AddReview, + }; + + public static ClaimDto ViewWarehouses { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewWarehouses, + }; + public static ClaimDto ManageWarehouses { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageWarehouses, + }; + + public static ClaimDto ViewShipping { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewShipping, + }; + public static ClaimDto ManageShipping { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageShipping, + }; + + public static ClaimDto ManageUsers { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageUsers, + }; + public static ClaimDto ViewUsers { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewUsers, + }; + + public static ClaimDto ManageFiles { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageFiles, + }; + public static ClaimDto ViewFiles { get; } = new ClaimDto + { + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ViewFiles, + }; + + public static List AllClaimDtos = new List + { + ManageBlogs, + ViewBlogs, + ManageBrands, + ViewBrands, + ManageCategories, + ViewCategories, + ManageDiscounts, + ViewDiscounts, + ManageOrders, + ViewAllOrders, + ViewMineOrders, + CreateOrder, + ManageProducts, + ViewProducts, + ManageReview, + AddReview, + ViewAllReviews, + ViewMineReviews, + ManageWarehouses, + ViewWarehouses, + ManageShipping, + ViewShipping, + ManageUsers, + ViewUsers, + ManageFiles, + ViewFiles + }; + + public static List AllClaims = new List + { + ManageBlogs.GetClaim, + ViewBlogs.GetClaim, + ManageBrands.GetClaim, + ViewBrands.GetClaim, + ManageCategories.GetClaim, + ViewCategories.GetClaim, + ManageDiscounts.GetClaim, + ViewDiscounts.GetClaim, + ManageOrders.GetClaim, + ViewAllOrders.GetClaim, + ViewMineOrders.GetClaim, + CreateOrder.GetClaim, + ManageProducts.GetClaim, + ViewProducts.GetClaim, + ManageReview.GetClaim, + AddReview.GetClaim, + ViewAllReviews.GetClaim, + ViewMineReviews.GetClaim, + ManageWarehouses.GetClaim, + ViewWarehouses.GetClaim, + ManageShipping.GetClaim, + ViewShipping.GetClaim, + ManageUsers.GetClaim, + ViewUsers.GetClaim, + ManageFiles.GetClaim, + ViewFiles.GetClaim + }; + + public static List CustomerClaims = new List + { + ViewBlogs.GetClaim, + ViewBrands.GetClaim, + ViewCategories.GetClaim, + ViewMineOrders.GetClaim, + CreateOrder.GetClaim, + ViewProducts.GetClaim, + AddReview.GetClaim, + ViewMineReviews.GetClaim, + }; + +} diff --git a/NetinaShop.Domain/Models/Claims/ApplicationPermission.cs b/NetinaShop.Domain/Models/Claims/ApplicationPermission.cs new file mode 100644 index 0000000..027faf0 --- /dev/null +++ b/NetinaShop.Domain/Models/Claims/ApplicationPermission.cs @@ -0,0 +1,41 @@ +namespace NetinaShop.Domain.Models.Claims; + +public static class ApplicationPermission +{ + public const string ManageBlogs = nameof(ManageBlogs); + public const string ViewBlogs = nameof(ManageBlogs); + + public const string ManageBrands = nameof(ManageBrands); + public const string ViewBrands = nameof(ViewBrands); + + public const string ManageCategories = nameof(ManageCategories); + public const string ViewCategories = nameof(ViewCategories); + + public const string ManageDiscounts = nameof(ManageDiscounts); + public const string ViewDiscounts = nameof(ViewDiscounts); + + public const string ManageOrders = nameof(ManageOrders); + public const string ViewAllOrders = nameof(ViewAllOrders); + public const string ViewMineOrders = nameof(ViewMineOrders); + public const string CreateOrder = nameof(CreateOrder); + + public const string ManageProducts = nameof(ManageProducts); + public const string ViewProducts = nameof(ViewProducts); + + public const string ManageReview = nameof(AddReview); + public const string AddReview = nameof(AddReview); + public const string ViewAllReviews = nameof(ViewAllReviews); + public const string ViewMineReviews = nameof(ViewMineReviews); + + public const string ManageWarehouses = nameof(ManageWarehouses); + public const string ViewWarehouses = nameof(ViewWarehouses); + + public const string ManageShipping = nameof(ManageShipping); + public const string ViewShipping = nameof(ViewShipping); + + public const string ManageUsers = nameof(ManageUsers); + public const string ViewUsers = nameof(ViewUsers); + + public const string ManageFiles = nameof(ManageFiles); + public const string ViewFiles = nameof(ViewFiles); +} \ No newline at end of file diff --git a/NetinaShop.Domain/Models/Claims/ClaimDto.cs b/NetinaShop.Domain/Models/Claims/ClaimDto.cs new file mode 100644 index 0000000..6b33ed4 --- /dev/null +++ b/NetinaShop.Domain/Models/Claims/ClaimDto.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.Claims; + +namespace NetinaShop.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/NetinaShop.Domain/Models/Claims/CustomClaimType.cs b/NetinaShop.Domain/Models/Claims/CustomClaimType.cs new file mode 100644 index 0000000..8206406 --- /dev/null +++ b/NetinaShop.Domain/Models/Claims/CustomClaimType.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Domain.Models.Claims +{ + public static class CustomClaimType + { + public static string Permission { get; } = "Permission"; + } +} \ No newline at end of file diff --git a/NetinaShop.Domain/Models/Settings/SiteSettings.cs b/NetinaShop.Domain/Models/Settings/SiteSettings.cs new file mode 100644 index 0000000..42fb700 --- /dev/null +++ b/NetinaShop.Domain/Models/Settings/SiteSettings.cs @@ -0,0 +1,37 @@ +namespace NetinaShop.Domain.Models.Settings; + +public class SiteSettings +{ + public JwtSettings JwtSettings { get; set; } = new JwtSettings(); + public string BaseUrl { get; set; } = string.Empty; + public RedisSettings MasterRedisConfiguration { get; set; } = new RedisSettings(); + public UserSetting UserSetting { get; set; } = new UserSetting(); + public string KaveNegarApiKey { get; set; } = string.Empty; +} +public class RedisSettings +{ + public string Password { get; set; } = string.Empty; + public string ServiceName { get; set; } = string.Empty; + public string Host { get; set; } = string.Empty; + public int Port { get; set; } +} + + +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/NetinaShop.Domain/NetinaShop.Domain.csproj b/NetinaShop.Domain/NetinaShop.Domain.csproj new file mode 100644 index 0000000..4fc64dd --- /dev/null +++ b/NetinaShop.Domain/NetinaShop.Domain.csproj @@ -0,0 +1,77 @@ + + + + + + net5.0 + 10 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Infrastructure/InfrastructureConfig.cs b/NetinaShop.Infrastructure/InfrastructureConfig.cs new file mode 100644 index 0000000..dd000ed --- /dev/null +++ b/NetinaShop.Infrastructure/InfrastructureConfig.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Infrastructure +{ + public class InfrastructureConfig + { + + } +} diff --git a/NetinaShop.Infrastructure/Models/DirectoryAddress.cs b/NetinaShop.Infrastructure/Models/DirectoryAddress.cs new file mode 100644 index 0000000..430f8b7 --- /dev/null +++ b/NetinaShop.Infrastructure/Models/DirectoryAddress.cs @@ -0,0 +1,7 @@ +namespace NetinaShop.Infrastructure.Models; + +public static class DirectoryAddress +{ + private static string _baseDire = $"{Directory.GetCurrentDirectory()}/wwwroot"; + public static string Logs = $"{_baseDire}/logs"; +} \ No newline at end of file diff --git a/NetinaShop.Infrastructure/NetinaShop.Infrastructure.csproj b/NetinaShop.Infrastructure/NetinaShop.Infrastructure.csproj new file mode 100644 index 0000000..01fe40e --- /dev/null +++ b/NetinaShop.Infrastructure/NetinaShop.Infrastructure.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Repository/Abstracts/ICurrentUserService.cs b/NetinaShop.Repository/Abstracts/ICurrentUserService.cs new file mode 100644 index 0000000..0b32269 --- /dev/null +++ b/NetinaShop.Repository/Abstracts/ICurrentUserService.cs @@ -0,0 +1,8 @@ +namespace NetinaShop.Repository.Abstracts; + +public interface ICurrentUserService : IScopedDependency +{ + string? UserId { get; } + string? RoleName { get; } + string? UserName { get; } +} \ No newline at end of file diff --git a/NetinaShop.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs b/NetinaShop.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs new file mode 100644 index 0000000..0f55141 --- /dev/null +++ b/NetinaShop.Repository/Extensions/DbContextOptionCustomExtensionsInfo.cs @@ -0,0 +1,59 @@ +namespace NetinaShop.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/NetinaShop.Repository/Extensions/ModelBuilderExtensions.cs b/NetinaShop.Repository/Extensions/ModelBuilderExtensions.cs new file mode 100644 index 0000000..066119c --- /dev/null +++ b/NetinaShop.Repository/Extensions/ModelBuilderExtensions.cs @@ -0,0 +1,190 @@ +namespace NetinaShop.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; + } + + /// + /// 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) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + modelBuilder.Entity(type); + builderQueryFilter.AddQueryFilterToModelBuilder(modelBuilder, type); + stopwatch.Stop(); + } + } + + /// + /// 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/NetinaShop.Repository/Migrations/20231211073413_init.Designer.cs b/NetinaShop.Repository/Migrations/20231211073413_init.Designer.cs new file mode 100644 index 0000000..681186a --- /dev/null +++ b/NetinaShop.Repository/Migrations/20231211073413_init.Designer.cs @@ -0,0 +1,1601 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetinaShop.Repository.Models; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NetinaShop.Repository.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20231211073413_init")] + partial class init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Claims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("Logins", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("Tokens", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("IsSuggested") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReadingTime") + .HasColumnType("integer"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summery") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Blogs", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("BlogCategories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("HasSpecialPage") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PageUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Brands", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.Discount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AmountType") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountPercent") + .HasColumnType("integer"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("ExpireDate") + .HasColumnType("timestamp without time zone"); + + b.Property("HasCode") + .HasColumnType("boolean"); + + b.Property("HasPriceCeiling") + .HasColumnType("boolean"); + + b.Property("HasPriceFloor") + .HasColumnType("boolean"); + + b.Property("IsForInvitation") + .HasColumnType("boolean"); + + b.Property("IsInfinity") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriceCeiling") + .HasColumnType("bigint"); + + b.Property("PriceFloor") + .HasColumnType("bigint"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UseCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Discounts", "public"); + + b.HasDiscriminator("Discriminator").HasValue("Discount"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeliveryPrice") + .HasColumnType("bigint"); + + b.Property("DiscountCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.Property("DiscountPrice") + .HasColumnType("bigint"); + + b.Property("DoneAt") + .HasColumnType("timestamp without time zone"); + + b.Property("IsPayed") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderAt") + .HasColumnType("timestamp without time zone"); + + b.Property("OrderStatus") + .HasColumnType("integer"); + + b.Property("PackingPrice") + .HasColumnType("bigint"); + + b.Property("PreparingMinute") + .HasColumnType("integer"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServicePrice") + .HasColumnType("bigint"); + + b.Property("TaxesPrice") + .HasColumnType("bigint"); + + b.Property("TotalPrice") + .HasColumnType("bigint"); + + b.Property("TotalProductsPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountId"); + + b.ToTable("Orders", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverPhoneNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDeliveries", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("OrderProductStatus") + .HasColumnType("integer"); + + b.Property("ProductCost") + .HasColumnType("real"); + + b.Property("ProductFee") + .HasColumnType("real"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderProducts", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BrandId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnglishName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpertCheck") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PersianName") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summery") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Warranty") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("BrandId"); + + b.ToTable("Products", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsBuyer") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Rate") + .HasColumnType("real"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("Reviews", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsFeature") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("ProductId"); + + b.ToTable("Specifications", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Sellers.Shipping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsFastShipping") + .HasColumnType("boolean"); + + b.Property("IsOriginalWarehouse") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("IsShipBySeller") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Shippings", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.StorageFiles.StorageFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("FileLocation") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileType") + .HasColumnType("integer"); + + b.Property("IsHeader") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("StorageFiles", "public"); + + b.HasDiscriminator("Discriminator").HasValue("StorageFile"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnglishName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PersianName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("Roles", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("BirthDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Gender") + .HasColumnType("integer"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NationalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("SignUpStatus") + .HasColumnType("integer"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("Users", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("LocationLat") + .HasColumnType("real"); + + b.Property("LocationLong") + .HasColumnType("real"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverPhoneNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserFavoriteProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserFavoriteProducts", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.CategoryDiscount", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.Discounts.Discount"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasIndex("CategoryId"); + + b.HasDiscriminator().HasValue("CategoryDiscount"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.ProductDiscount", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.Discounts.Discount"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.HasIndex("ProductId"); + + b.HasDiscriminator().HasValue("ProductDiscount"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("BlogId") + .HasColumnType("uuid"); + + b.HasIndex("BlogId"); + + b.HasDiscriminator().HasValue("BlogStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.BrandStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("BrandId") + .HasColumnType("uuid"); + + b.HasIndex("BrandId"); + + b.HasDiscriminator().HasValue("BrandStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.CategoryStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasIndex("CategoryId"); + + b.HasDiscriminator().HasValue("CategoryStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.HasIndex("ProductId"); + + b.HasDiscriminator().HasValue("ProductStorageFile"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.HasOne("NetinaShop.Domain.Entities.Blogs.BlogCategory", "Category") + .WithMany("Blogs") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.HasOne("NetinaShop.Domain.Entities.Discounts.Discount", null) + .WithMany("Orders") + .HasForeignKey("DiscountId"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderDelivery", b => + { + b.HasOne("NetinaShop.Domain.Entities.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderProduct", b => + { + b.HasOne("NetinaShop.Domain.Entities.Orders.Order", "Order") + .WithMany("OrderProducts") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("OrderProducts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.HasOne("NetinaShop.Domain.Entities.Brands.Brand", "Brand") + .WithMany("Products") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductCategory", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Categories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Review", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Reviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Specification", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Specifications") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Parent"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserAddress", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany("Addresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserFavoriteProduct", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.CategoryDiscount", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.ProductDiscount", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Blogs.Blog", "Blog") + .WithMany("Files") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Blog"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.BrandStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Brands.Brand", "Brand") + .WithMany("Files") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.CategoryStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany("Files") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Files") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogCategory", b => + { + b.Navigation("Blogs"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.Brand", b => + { + b.Navigation("Files"); + + b.Navigation("Products"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.Navigation("Children"); + + b.Navigation("Files"); + + b.Navigation("Products"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.Discount", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.Navigation("OrderProducts"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.Navigation("Categories"); + + b.Navigation("Files"); + + b.Navigation("OrderProducts"); + + b.Navigation("Reviews"); + + b.Navigation("Specifications"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationUser", b => + { + b.Navigation("Addresses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/NetinaShop.Repository/Migrations/20231211073413_init.cs b/NetinaShop.Repository/Migrations/20231211073413_init.cs new file mode 100644 index 0000000..ae94041 --- /dev/null +++ b/NetinaShop.Repository/Migrations/20231211073413_init.cs @@ -0,0 +1,1020 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NetinaShop.Repository.Migrations +{ + /// + public partial class init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "public"); + + migrationBuilder.CreateTable( + name: "BlogCategories", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Brands", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + HasSpecialPage = table.Column(type: "boolean", nullable: false), + PageUrl = table.Column(type: "text", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Brands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Categories", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + ParentId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + table.ForeignKey( + name: "FK_Categories_Categories_ParentId", + column: x => x.ParentId, + principalSchema: "public", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Roles", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Description = table.Column(type: "text", nullable: false), + EnglishName = table.Column(type: "text", nullable: false), + PersianName = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Shippings", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + WarehouseName = table.Column(type: "text", nullable: false), + IsFastShipping = table.Column(type: "boolean", nullable: false), + IsShipBySeller = table.Column(type: "boolean", nullable: false), + IsOriginalWarehouse = table.Column(type: "boolean", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Shippings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + FirstName = table.Column(type: "text", nullable: false), + LastName = table.Column(type: "text", nullable: false), + NationalId = table.Column(type: "text", nullable: false), + BirthDate = table.Column(type: "timestamp without time zone", nullable: false), + Gender = table.Column(type: "integer", nullable: false), + SignUpStatus = table.Column(type: "integer", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Blogs", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + Content = table.Column(type: "text", nullable: false), + Tags = table.Column(type: "text", nullable: false), + ReadingTime = table.Column(type: "integer", nullable: false), + Summery = table.Column(type: "text", nullable: false), + IsSuggested = table.Column(type: "boolean", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Blogs", x => x.Id); + table.ForeignKey( + name: "FK_Blogs_BlogCategories_CategoryId", + column: x => x.CategoryId, + principalSchema: "public", + principalTable: "BlogCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Products", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + PersianName = table.Column(type: "text", nullable: false), + EnglishName = table.Column(type: "text", nullable: false), + Summery = table.Column(type: "text", nullable: false), + ExpertCheck = table.Column(type: "text", nullable: false), + Tags = table.Column(type: "text", nullable: false), + Warranty = table.Column(type: "text", nullable: false), + BrandId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + table.ForeignKey( + name: "FK_Products_Brands_BrandId", + column: x => x.BrandId, + principalSchema: "public", + principalTable: "Brands", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "RoleClaims", + schema: "public", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "uuid", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_RoleClaims_Roles_RoleId", + column: x => x.RoleId, + principalSchema: "public", + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Claims", + schema: "public", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "uuid", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Claims", x => x.Id); + table.ForeignKey( + name: "FK_Claims_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Logins", + schema: "public", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Logins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_Logins_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Tokens", + schema: "public", + columns: table => new + { + UserId = table.Column(type: "uuid", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_Tokens_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "UserAddresses", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Address = table.Column(type: "text", nullable: false), + PostalCode = table.Column(type: "text", nullable: false), + ReceiverFullName = table.Column(type: "text", nullable: false), + ReceiverPhoneNumber = table.Column(type: "text", nullable: false), + LocationLat = table.Column(type: "real", nullable: false), + LocationLong = table.Column(type: "real", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserAddresses", x => x.Id); + table.ForeignKey( + name: "FK_UserAddresses_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + schema: "public", + columns: table => new + { + UserId = table.Column(type: "uuid", nullable: false), + RoleId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_UserRoles_Roles_RoleId", + column: x => x.RoleId, + principalSchema: "public", + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_UserRoles_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Discounts", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "text", nullable: false), + DiscountPercent = table.Column(type: "integer", nullable: false), + DiscountAmount = table.Column(type: "bigint", nullable: false), + HasCode = table.Column(type: "boolean", nullable: false), + AmountType = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "integer", nullable: false), + Count = table.Column(type: "integer", nullable: false), + StartDate = table.Column(type: "timestamp without time zone", nullable: false), + ExpireDate = table.Column(type: "timestamp without time zone", nullable: false), + PriceFloor = table.Column(type: "bigint", nullable: false), + HasPriceFloor = table.Column(type: "boolean", nullable: false), + PriceCeiling = table.Column(type: "bigint", nullable: false), + HasPriceCeiling = table.Column(type: "boolean", nullable: false), + IsInfinity = table.Column(type: "boolean", nullable: false), + UseCount = table.Column(type: "bigint", nullable: false), + IsForInvitation = table.Column(type: "boolean", nullable: false), + Discriminator = table.Column(type: "character varying(21)", maxLength: 21, nullable: false), + CategoryId = table.Column(type: "uuid", nullable: true), + ProductId = table.Column(type: "uuid", nullable: true), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Discounts", x => x.Id); + table.ForeignKey( + name: "FK_Discounts_Categories_CategoryId", + column: x => x.CategoryId, + principalSchema: "public", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Discounts_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProductCategories", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductCategories", x => x.Id); + table.ForeignKey( + name: "FK_ProductCategories_Categories_CategoryId", + column: x => x.CategoryId, + principalSchema: "public", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ProductCategories_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Reviews", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + Comment = table.Column(type: "text", nullable: false), + Rate = table.Column(type: "real", nullable: false), + IsBuyer = table.Column(type: "boolean", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reviews", x => x.Id); + table.ForeignKey( + name: "FK_Reviews_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Reviews_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Specifications", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + Detail = table.Column(type: "text", nullable: false), + IsFeature = table.Column(type: "boolean", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + ParentId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Specifications", x => x.Id); + table.ForeignKey( + name: "FK_Specifications_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Specifications_Specifications_ParentId", + column: x => x.ParentId, + principalSchema: "public", + principalTable: "Specifications", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "StorageFiles", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + FileLocation = table.Column(type: "text", nullable: false), + FileName = table.Column(type: "text", nullable: false), + IsHeader = table.Column(type: "boolean", nullable: false), + IsPrimary = table.Column(type: "boolean", nullable: false), + FileType = table.Column(type: "integer", nullable: false), + Discriminator = table.Column(type: "character varying(21)", maxLength: 21, nullable: false), + BlogId = table.Column(type: "uuid", nullable: true), + BrandId = table.Column(type: "uuid", nullable: true), + CategoryId = table.Column(type: "uuid", nullable: true), + ProductId = table.Column(type: "uuid", nullable: true), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StorageFiles", x => x.Id); + table.ForeignKey( + name: "FK_StorageFiles_Blogs_BlogId", + column: x => x.BlogId, + principalSchema: "public", + principalTable: "Blogs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_StorageFiles_Brands_BrandId", + column: x => x.BrandId, + principalSchema: "public", + principalTable: "Brands", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_StorageFiles_Categories_CategoryId", + column: x => x.CategoryId, + principalSchema: "public", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_StorageFiles_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "UserFavoriteProducts", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserFavoriteProducts", x => x.Id); + table.ForeignKey( + name: "FK_UserFavoriteProducts_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_UserFavoriteProducts_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Orders", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TotalPrice = table.Column(type: "bigint", nullable: false), + DeliveryPrice = table.Column(type: "bigint", nullable: false), + TaxesPrice = table.Column(type: "bigint", nullable: false), + ServicePrice = table.Column(type: "bigint", nullable: false), + PackingPrice = table.Column(type: "bigint", nullable: false), + TotalProductsPrice = table.Column(type: "bigint", nullable: false), + DiscountPrice = table.Column(type: "bigint", nullable: false), + IsPayed = table.Column(type: "boolean", nullable: false), + OrderStatus = table.Column(type: "integer", nullable: false), + DoneAt = table.Column(type: "timestamp without time zone", nullable: false), + OrderAt = table.Column(type: "timestamp without time zone", nullable: false), + PreparingMinute = table.Column(type: "integer", nullable: false), + DiscountCode = table.Column(type: "text", nullable: false), + DiscountId = table.Column(type: "uuid", nullable: true), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Discounts_DiscountId", + column: x => x.DiscountId, + principalSchema: "public", + principalTable: "Discounts", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "OrderDeliveries", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Address = table.Column(type: "text", nullable: false), + PostalCode = table.Column(type: "text", nullable: false), + ReceiverPhoneNumber = table.Column(type: "text", nullable: false), + ReceiverFullName = table.Column(type: "text", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderDeliveries", x => x.Id); + table.ForeignKey( + name: "FK_OrderDeliveries_Orders_OrderId", + column: x => x.OrderId, + principalSchema: "public", + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "OrderProducts", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Count = table.Column(type: "integer", nullable: false), + ProductFee = table.Column(type: "real", nullable: false), + ProductCost = table.Column(type: "real", nullable: false), + OrderProductStatus = table.Column(type: "integer", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + RemovedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + IsRemoved = table.Column(type: "boolean", nullable: false), + RemovedBy = table.Column(type: "text", nullable: false), + ModifiedAt = table.Column(type: "timestamp without time zone", nullable: false), + ModifiedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderProducts", x => x.Id); + table.ForeignKey( + name: "FK_OrderProducts_Orders_OrderId", + column: x => x.OrderId, + principalSchema: "public", + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_OrderProducts_Products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Blogs_CategoryId", + schema: "public", + table: "Blogs", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Categories_ParentId", + schema: "public", + table: "Categories", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_Claims_UserId", + schema: "public", + table: "Claims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Discounts_CategoryId", + schema: "public", + table: "Discounts", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Discounts_ProductId", + schema: "public", + table: "Discounts", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Logins_UserId", + schema: "public", + table: "Logins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderDeliveries_OrderId", + schema: "public", + table: "OrderDeliveries", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderProducts_OrderId", + schema: "public", + table: "OrderProducts", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderProducts_ProductId", + schema: "public", + table: "OrderProducts", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_DiscountId", + schema: "public", + table: "Orders", + column: "DiscountId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductCategories_CategoryId", + schema: "public", + table: "ProductCategories", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductCategories_ProductId", + schema: "public", + table: "ProductCategories", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Products_BrandId", + schema: "public", + table: "Products", + column: "BrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_ProductId", + schema: "public", + table: "Reviews", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_UserId", + schema: "public", + table: "Reviews", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RoleClaims_RoleId", + schema: "public", + table: "RoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + schema: "public", + table: "Roles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Specifications_ParentId", + schema: "public", + table: "Specifications", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_Specifications_ProductId", + schema: "public", + table: "Specifications", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_StorageFiles_BlogId", + schema: "public", + table: "StorageFiles", + column: "BlogId"); + + migrationBuilder.CreateIndex( + name: "IX_StorageFiles_BrandId", + schema: "public", + table: "StorageFiles", + column: "BrandId"); + + migrationBuilder.CreateIndex( + name: "IX_StorageFiles_CategoryId", + schema: "public", + table: "StorageFiles", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_StorageFiles_ProductId", + schema: "public", + table: "StorageFiles", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_UserAddresses_UserId", + schema: "public", + table: "UserAddresses", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserFavoriteProducts_ProductId", + schema: "public", + table: "UserFavoriteProducts", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_UserFavoriteProducts_UserId", + schema: "public", + table: "UserFavoriteProducts", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_RoleId", + schema: "public", + table: "UserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + schema: "public", + table: "Users", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + schema: "public", + table: "Users", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Claims", + schema: "public"); + + migrationBuilder.DropTable( + name: "Logins", + schema: "public"); + + migrationBuilder.DropTable( + name: "OrderDeliveries", + schema: "public"); + + migrationBuilder.DropTable( + name: "OrderProducts", + schema: "public"); + + migrationBuilder.DropTable( + name: "ProductCategories", + schema: "public"); + + migrationBuilder.DropTable( + name: "Reviews", + schema: "public"); + + migrationBuilder.DropTable( + name: "RoleClaims", + schema: "public"); + + migrationBuilder.DropTable( + name: "Shippings", + schema: "public"); + + migrationBuilder.DropTable( + name: "Specifications", + schema: "public"); + + migrationBuilder.DropTable( + name: "StorageFiles", + schema: "public"); + + migrationBuilder.DropTable( + name: "Tokens", + schema: "public"); + + migrationBuilder.DropTable( + name: "UserAddresses", + schema: "public"); + + migrationBuilder.DropTable( + name: "UserFavoriteProducts", + schema: "public"); + + migrationBuilder.DropTable( + name: "UserRoles", + schema: "public"); + + migrationBuilder.DropTable( + name: "Orders", + schema: "public"); + + migrationBuilder.DropTable( + name: "Blogs", + schema: "public"); + + migrationBuilder.DropTable( + name: "Roles", + schema: "public"); + + migrationBuilder.DropTable( + name: "Users", + schema: "public"); + + migrationBuilder.DropTable( + name: "Discounts", + schema: "public"); + + migrationBuilder.DropTable( + name: "BlogCategories", + schema: "public"); + + migrationBuilder.DropTable( + name: "Categories", + schema: "public"); + + migrationBuilder.DropTable( + name: "Products", + schema: "public"); + + migrationBuilder.DropTable( + name: "Brands", + schema: "public"); + } + } +} diff --git a/NetinaShop.Repository/Migrations/ApplicationContextModelSnapshot.cs b/NetinaShop.Repository/Migrations/ApplicationContextModelSnapshot.cs new file mode 100644 index 0000000..c0d69d5 --- /dev/null +++ b/NetinaShop.Repository/Migrations/ApplicationContextModelSnapshot.cs @@ -0,0 +1,1598 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetinaShop.Repository.Models; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NetinaShop.Repository.Migrations +{ + [DbContext(typeof(ApplicationContext))] + partial class ApplicationContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Claims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("Logins", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("Tokens", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("IsSuggested") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReadingTime") + .HasColumnType("integer"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summery") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Blogs", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("BlogCategories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("HasSpecialPage") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PageUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Brands", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.Discount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AmountType") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountPercent") + .HasColumnType("integer"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("ExpireDate") + .HasColumnType("timestamp without time zone"); + + b.Property("HasCode") + .HasColumnType("boolean"); + + b.Property("HasPriceCeiling") + .HasColumnType("boolean"); + + b.Property("HasPriceFloor") + .HasColumnType("boolean"); + + b.Property("IsForInvitation") + .HasColumnType("boolean"); + + b.Property("IsInfinity") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriceCeiling") + .HasColumnType("bigint"); + + b.Property("PriceFloor") + .HasColumnType("bigint"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UseCount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Discounts", "public"); + + b.HasDiscriminator("Discriminator").HasValue("Discount"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeliveryPrice") + .HasColumnType("bigint"); + + b.Property("DiscountCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.Property("DiscountPrice") + .HasColumnType("bigint"); + + b.Property("DoneAt") + .HasColumnType("timestamp without time zone"); + + b.Property("IsPayed") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderAt") + .HasColumnType("timestamp without time zone"); + + b.Property("OrderStatus") + .HasColumnType("integer"); + + b.Property("PackingPrice") + .HasColumnType("bigint"); + + b.Property("PreparingMinute") + .HasColumnType("integer"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServicePrice") + .HasColumnType("bigint"); + + b.Property("TaxesPrice") + .HasColumnType("bigint"); + + b.Property("TotalPrice") + .HasColumnType("bigint"); + + b.Property("TotalProductsPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountId"); + + b.ToTable("Orders", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderDelivery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverPhoneNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDeliveries", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("OrderProductStatus") + .HasColumnType("integer"); + + b.Property("ProductCost") + .HasColumnType("real"); + + b.Property("ProductFee") + .HasColumnType("real"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderProducts", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BrandId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnglishName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpertCheck") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PersianName") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summery") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Warranty") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("BrandId"); + + b.ToTable("Products", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsBuyer") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Rate") + .HasColumnType("real"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("Reviews", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsFeature") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("ProductId"); + + b.ToTable("Specifications", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Sellers.Shipping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsFastShipping") + .HasColumnType("boolean"); + + b.Property("IsOriginalWarehouse") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("IsShipBySeller") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Shippings", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.StorageFiles.StorageFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("FileLocation") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileType") + .HasColumnType("integer"); + + b.Property("IsHeader") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("StorageFiles", "public"); + + b.HasDiscriminator("Discriminator").HasValue("StorageFile"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnglishName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PersianName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("Roles", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("BirthDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Gender") + .HasColumnType("integer"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NationalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("SignUpStatus") + .HasColumnType("integer"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("Users", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("LocationLat") + .HasColumnType("real"); + + b.Property("LocationLong") + .HasColumnType("real"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReceiverPhoneNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserFavoriteProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRemoved") + .HasColumnType("boolean"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("RemovedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RemovedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserFavoriteProducts", "public"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.CategoryDiscount", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.Discounts.Discount"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasIndex("CategoryId"); + + b.HasDiscriminator().HasValue("CategoryDiscount"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.ProductDiscount", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.Discounts.Discount"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.HasIndex("ProductId"); + + b.HasDiscriminator().HasValue("ProductDiscount"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("BlogId") + .HasColumnType("uuid"); + + b.HasIndex("BlogId"); + + b.HasDiscriminator().HasValue("BlogStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.BrandStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("BrandId") + .HasColumnType("uuid"); + + b.HasIndex("BrandId"); + + b.HasDiscriminator().HasValue("BrandStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.CategoryStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasIndex("CategoryId"); + + b.HasDiscriminator().HasValue("CategoryStorageFile"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductStorageFile", b => + { + b.HasBaseType("NetinaShop.Domain.Entities.StorageFiles.StorageFile"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.HasIndex("ProductId"); + + b.HasDiscriminator().HasValue("ProductStorageFile"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.HasOne("NetinaShop.Domain.Entities.Blogs.BlogCategory", "Category") + .WithMany("Blogs") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.HasOne("NetinaShop.Domain.Entities.Discounts.Discount", null) + .WithMany("Orders") + .HasForeignKey("DiscountId"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderDelivery", b => + { + b.HasOne("NetinaShop.Domain.Entities.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.OrderProduct", b => + { + b.HasOne("NetinaShop.Domain.Entities.Orders.Order", "Order") + .WithMany("OrderProducts") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("OrderProducts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.HasOne("NetinaShop.Domain.Entities.Brands.Brand", "Brand") + .WithMany("Products") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductCategory", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Categories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Review", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Reviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Specification", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Specifications") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Parent"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserAddress", b => + { + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany("Addresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.UserFavoriteProduct", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.CategoryDiscount", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.ProductDiscount", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Blogs.Blog", "Blog") + .WithMany("Files") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Blog"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.BrandStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Brands.Brand", "Brand") + .WithMany("Files") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.CategoryStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Categories.Category", "Category") + .WithMany("Files") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.ProductStorageFile", b => + { + b.HasOne("NetinaShop.Domain.Entities.Products.Product", "Product") + .WithMany("Files") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.BlogCategory", b => + { + b.Navigation("Blogs"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Brands.Brand", b => + { + b.Navigation("Files"); + + b.Navigation("Products"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Categories.Category", b => + { + b.Navigation("Children"); + + b.Navigation("Files"); + + b.Navigation("Products"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Discounts.Discount", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Orders.Order", b => + { + b.Navigation("OrderProducts"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Product", b => + { + b.Navigation("Categories"); + + b.Navigation("Files"); + + b.Navigation("OrderProducts"); + + b.Navigation("Reviews"); + + b.Navigation("Specifications"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Products.Specification", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("NetinaShop.Domain.Entities.Users.ApplicationUser", b => + { + b.Navigation("Addresses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/NetinaShop.Repository/Models/ApplicationContext.cs b/NetinaShop.Repository/Models/ApplicationContext.cs new file mode 100644 index 0000000..be03815 --- /dev/null +++ b/NetinaShop.Repository/Models/ApplicationContext.cs @@ -0,0 +1,47 @@ +namespace NetinaShop.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/NetinaShop.Repository/NetinaShop.Repository.csproj b/NetinaShop.Repository/NetinaShop.Repository.csproj new file mode 100644 index 0000000..bd09f1b --- /dev/null +++ b/NetinaShop.Repository/NetinaShop.Repository.csproj @@ -0,0 +1,71 @@ + + + + net8.0 + enable + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NetinaShop.Repository/Repositories/Base/BaseRepository.cs b/NetinaShop.Repository/Repositories/Base/BaseRepository.cs new file mode 100644 index 0000000..37810f8 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/BaseRepository.cs @@ -0,0 +1,109 @@ +namespace NetinaShop.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) + { + return await Entities.FindAsync(ids, cancellationToken); + } + + #region Sync Methods + + public virtual T GetById(params object[] ids) + { + var ent = Entities.Find(ids); + Detach(ent); + return ent; + } + + 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 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); + 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); + } + + #endregion + } +} \ No newline at end of file diff --git a/NetinaShop.Repository/Repositories/Base/Contracts/IBaseRepository.cs b/NetinaShop.Repository/Repositories/Base/Contracts/IBaseRepository.cs new file mode 100644 index 0000000..23e3713 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Contracts/IBaseRepository.cs @@ -0,0 +1,6 @@ +namespace NetinaShop.Repository.Repositories.Base.Contracts +{ + public interface IBaseRepository : IDisposable, IReadRepository, IWriteRepository where T : class, IApiEntity + { + } +} \ No newline at end of file diff --git a/NetinaShop.Repository/Repositories/Base/Contracts/IReadRepository.cs b/NetinaShop.Repository/Repositories/Base/Contracts/IReadRepository.cs new file mode 100644 index 0000000..73c4a19 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Contracts/IReadRepository.cs @@ -0,0 +1,13 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/Contracts/IRepository.cs b/NetinaShop.Repository/Repositories/Base/Contracts/IRepository.cs new file mode 100644 index 0000000..714439a --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Contracts/IRepository.cs @@ -0,0 +1,10 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs b/NetinaShop.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs new file mode 100644 index 0000000..3e9a202 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Contracts/IRepositoryWrapper.cs @@ -0,0 +1,11 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/Contracts/IWriteRepository.cs b/NetinaShop.Repository/Repositories/Base/Contracts/IWriteRepository.cs new file mode 100644 index 0000000..ee73f2e --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Contracts/IWriteRepository.cs @@ -0,0 +1,15 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/ReadRepository.cs b/NetinaShop.Repository/Repositories/Base/ReadRepository.cs new file mode 100644 index 0000000..b4cc690 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/ReadRepository.cs @@ -0,0 +1,43 @@ +namespace NetinaShop.Repository.Repositories.Base +{ + public class ReadRepository : Repository, IDisposable, IReadRepository where T : class, IApiEntity + { + public ReadRepository( + ApplicationContext dbContext) : base(dbContext) + { + } + + public void Dispose() + { + DbContext?.Dispose(); + } + + + public virtual T GetById(params object[] ids) + { + var ent = Entities.Find(ids); + 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); + 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/NetinaShop.Repository/Repositories/Base/Repository.cs b/NetinaShop.Repository/Repositories/Base/Repository.cs new file mode 100644 index 0000000..df4f649 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/Repository.cs @@ -0,0 +1,36 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/RepositoryWrapper.cs b/NetinaShop.Repository/Repositories/Base/RepositoryWrapper.cs new file mode 100644 index 0000000..9c95177 --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/RepositoryWrapper.cs @@ -0,0 +1,64 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/Base/WriteRepository.cs b/NetinaShop.Repository/Repositories/Base/WriteRepository.cs new file mode 100644 index 0000000..8dda98b --- /dev/null +++ b/NetinaShop.Repository/Repositories/Base/WriteRepository.cs @@ -0,0 +1,95 @@ + +namespace NetinaShop.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/NetinaShop.Repository/Repositories/UnitOfWork/IUnitOfWork.cs b/NetinaShop.Repository/Repositories/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 0000000..98d101f --- /dev/null +++ b/NetinaShop.Repository/Repositories/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,9 @@ +namespace NetinaShop.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/NetinaShop.Repository/Repositories/UnitOfWork/UnitOfWork.cs b/NetinaShop.Repository/Repositories/UnitOfWork/UnitOfWork.cs new file mode 100644 index 0000000..a9395fa --- /dev/null +++ b/NetinaShop.Repository/Repositories/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,61 @@ +namespace NetinaShop.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/NetinaShop.Repository/RepositoryConfig.cs b/NetinaShop.Repository/RepositoryConfig.cs new file mode 100644 index 0000000..56934de --- /dev/null +++ b/NetinaShop.Repository/RepositoryConfig.cs @@ -0,0 +1,18 @@ +namespace NetinaShop.Repository; + +public static class RepositoryConfig +{ + public static async Task InitialDb(this IApplicationBuilder app) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using (var scope = scopeFactory.CreateScope()) + { + var identityDbInitialize = scope.ServiceProvider.GetService(); + if (identityDbInitialize != null) + { + identityDbInitialize.Initialize(); + await identityDbInitialize.SeedDate(); + } + } + } +} \ No newline at end of file diff --git a/NetinaShop.Repository/Services/Abstracts/IDbInitializerService.cs b/NetinaShop.Repository/Services/Abstracts/IDbInitializerService.cs new file mode 100644 index 0000000..1d2b938 --- /dev/null +++ b/NetinaShop.Repository/Services/Abstracts/IDbInitializerService.cs @@ -0,0 +1,9 @@ +namespace NetinaShop.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/NetinaShop.Repository/Services/DbInitializerService.cs b/NetinaShop.Repository/Services/DbInitializerService.cs new file mode 100644 index 0000000..a4ef57e --- /dev/null +++ b/NetinaShop.Repository/Services/DbInitializerService.cs @@ -0,0 +1,105 @@ +namespace NetinaShop.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 seedAdmin = _adminUserSeedOptions.Value.UserSetting; + var user = await _userManager.FindByNameAsync(seedAdmin.Username); + if (user == null) + { + var 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 adminUserResult = await _userManager.CreateAsync(adminUser, seedAdmin.Password); + if (adminUserResult.Succeeded) await _userManager.AddToRoleAsync(adminUser, seedAdmin.RoleName); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public async Task SeedRoles() + { + var seedAdmin = _adminUserSeedOptions.Value.UserSetting; + var managerRole = await _roleManager.FindByNameAsync(seedAdmin.RoleName); + + if (managerRole == null) + { + managerRole = new ApplicationRole + { + Name = seedAdmin.RoleName, + EnglishName = seedAdmin.RoleName, + Description = "root 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 = "مشتری", + 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/NetinaShop.sln b/NetinaShop.sln new file mode 100644 index 0000000..58e05c2 --- /dev/null +++ b/NetinaShop.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34316.72 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetinaShop.Api", "NetinaShop.Api\NetinaShop.Api.csproj", "{12DD0CD6-93AF-4990-8121-E12A05AA75AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetinaShop.Common", "NetinaShop.Common\NetinaShop.Common.csproj", "{53171FE6-AE59-4696-9A8B-FF7CCBF921CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetinaShop.Domain", "NetinaShop.Domain\NetinaShop.Domain.csproj", "{A6890803-432B-4045-AF5A-DC5C0B9F8D0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetinaShop.Infrastructure", "NetinaShop.Infrastructure\NetinaShop.Infrastructure.csproj", "{A3AB0DD7-5472-4A1C-9CF4-D0F97B926CF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetinaShop.Core", "NetinaShop.Core\NetinaShop.Core.csproj", "{D422399C-A65D-479B-975C-40202E9C16C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetinaShop.Repository", "NetinaShop.Repository\NetinaShop.Repository.csproj", "{4A25936E-275C-41EA-9ADC-4375E8AC5207}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {12DD0CD6-93AF-4990-8121-E12A05AA75AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12DD0CD6-93AF-4990-8121-E12A05AA75AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12DD0CD6-93AF-4990-8121-E12A05AA75AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12DD0CD6-93AF-4990-8121-E12A05AA75AD}.Release|Any CPU.Build.0 = Release|Any CPU + {53171FE6-AE59-4696-9A8B-FF7CCBF921CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53171FE6-AE59-4696-9A8B-FF7CCBF921CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53171FE6-AE59-4696-9A8B-FF7CCBF921CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53171FE6-AE59-4696-9A8B-FF7CCBF921CB}.Release|Any CPU.Build.0 = Release|Any CPU + {A6890803-432B-4045-AF5A-DC5C0B9F8D0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6890803-432B-4045-AF5A-DC5C0B9F8D0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6890803-432B-4045-AF5A-DC5C0B9F8D0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6890803-432B-4045-AF5A-DC5C0B9F8D0D}.Release|Any CPU.Build.0 = Release|Any CPU + {A3AB0DD7-5472-4A1C-9CF4-D0F97B926CF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3AB0DD7-5472-4A1C-9CF4-D0F97B926CF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3AB0DD7-5472-4A1C-9CF4-D0F97B926CF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3AB0DD7-5472-4A1C-9CF4-D0F97B926CF8}.Release|Any CPU.Build.0 = Release|Any CPU + {D422399C-A65D-479B-975C-40202E9C16C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D422399C-A65D-479B-975C-40202E9C16C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D422399C-A65D-479B-975C-40202E9C16C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D422399C-A65D-479B-975C-40202E9C16C3}.Release|Any CPU.Build.0 = Release|Any CPU + {4A25936E-275C-41EA-9ADC-4375E8AC5207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A25936E-275C-41EA-9ADC-4375E8AC5207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A25936E-275C-41EA-9ADC-4375E8AC5207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A25936E-275C-41EA-9ADC-4375E8AC5207}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3740B4AA-7AFB-4DAC-87CB-13AD7D1DC814} + EndGlobalSection +EndGlobal