Add project files.
parent
21279851fe
commit
ed4410d191
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"mapster.tool": {
|
||||
"version": "8.4.0",
|
||||
"commands": [
|
||||
"dotnet-mapster"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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/**
|
|
@ -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": "*"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -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": "*"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -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<IResult> SignUpComplex([FromBody] SignUpRequestDto request, IAccountService accountService, CancellationToken cancellationToken) =>
|
||||
TypedResults.Ok(await accountService.CompleteSignUpAsync(request, cancellationToken));
|
||||
|
||||
public async Task<IResult> LoginWithPassword([FromBody] LoginRequestDto loginRequestDto, IAccountService accountService, CancellationToken cancellationToken) =>
|
||||
TypedResults.Ok(await accountService.LoginWithPasswordAsync(loginRequestDto.UserName, loginRequestDto.Password, cancellationToken));
|
||||
|
||||
|
||||
public async Task<IResult> LoginWithVerifyCode([FromBody] LoginRequestDto loginRequestDto, IAccountService accountService, CancellationToken cancellationToken) =>
|
||||
TypedResults.Ok(await accountService.LoginWithVerifyCodeAsync(loginRequestDto.UserName, loginRequestDto.VerifyCode, cancellationToken));
|
||||
|
||||
|
||||
public async Task<IResult> GetVerifyCodeCode([FromQuery] string phoneNumber, IAccountService accountService) =>
|
||||
TypedResults.Ok(await accountService.GetVerifyCodeAsync(phoneNumber));
|
||||
|
||||
|
||||
public async Task<IResult> ForgetPassword([FromQuery] string phoneNumber, IAccountService accountService) =>
|
||||
TypedResults.Ok(await accountService.ForgetPasswordAsync(phoneNumber));
|
||||
|
||||
|
||||
public async Task<IResult> 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<IResult> GetAllAsync([FromQuery] int page, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken)
|
||||
=> TypedResults.Ok(await repositoryWrapper.SetRepository<Blog>().TableNoTracking.OrderByDescending(b=>b.CreatedAt).Skip(page*10).Take(10).ToListAsync(cancellationToken));
|
||||
|
||||
// GET:Get An Entity By Id
|
||||
public async Task<IResult> GetAsync(Guid id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken)
|
||||
=> TypedResults.Ok(await repositoryWrapper.SetRepository<Blog>().TableNoTracking.Where(b=>b.Id==id).FirstOrDefaultAsync(cancellationToken));
|
||||
|
||||
// POST:Create Entity
|
||||
public async Task<IResult> Post([FromBody] Blog ent, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
repositoryWrapper.SetRepository<Blog>().Add(ent);
|
||||
await repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
// PUT:Update Entity
|
||||
public async Task<IResult> Put([FromBody] Blog updated, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await repositoryWrapper.SetRepository<Blog>().TableNoTracking
|
||||
.FirstOrDefaultAsync(b => b.Id == updated.Id, cancellationToken);
|
||||
if (ent == null)
|
||||
throw new AppException("Blog not found");
|
||||
repositoryWrapper.SetRepository<Blog>().Update(updated);
|
||||
await repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
// DELETE:Delete Entity
|
||||
public async Task<IResult> Delete(Guid id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await repositoryWrapper.SetRepository<Blog>().TableNoTracking
|
||||
.FirstOrDefaultAsync(b => b.Id == id, cancellationToken);
|
||||
if (ent == null)
|
||||
throw new AppException("Blog not found");
|
||||
repositoryWrapper.SetRepository<Blog>().Delete(ent);
|
||||
await repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
}
|
|
@ -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"]
|
|
@ -0,0 +1,124 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
|
||||
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Ben.BlockingDetector" Version="0.0.4" />
|
||||
<PackageReference Include="Carter" Version="8.0.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.8.1" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.8.1" />
|
||||
<PackageReference Include="MediatR.Extensions.Autofac.DependencyInjection" Version="11.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Autofac" Version="7.1.0" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Elmah.Io.AspNetCore.Serilog" Version="5.0.17" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
||||
<PackageReference Include="Sentry.Serilog" Version="3.41.3" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.PostgreSQL" Version="2.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.ElmahIo" Version="5.0.38" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="9.1.0" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.Core" Version="9.1.0" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.Newtonsoft" Version="9.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.12" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Asp.Versioning" />
|
||||
<Using Include="AspNetCoreRateLimit" />
|
||||
<Using Include="AspNetCoreRateLimit.Redis" />
|
||||
<Using Include="Autofac" />
|
||||
<Using Include="Autofac.Extensions.DependencyInjection" />
|
||||
<Using Include="FluentValidation" />
|
||||
<Using Include="MediatR.Extensions.Autofac.DependencyInjection" />
|
||||
<Using Include="MediatR.Extensions.Autofac.DependencyInjection.Builder" />
|
||||
<Using Include="Microsoft.AspNetCore.ResponseCompression" />
|
||||
<Using Include="NetinaShop.Api.WebFramework.Swagger" />
|
||||
<Using Include="NetinaShop.Common.Extensions" />
|
||||
<Using Include="NetinaShop.Common.Models" />
|
||||
<Using Include="NetinaShop.Common.Models.Api" />
|
||||
<Using Include="NetinaShop.Common.Models.Entity" />
|
||||
<Using Include="NetinaShop.Common.Models.Exception" />
|
||||
<Using Include="NetinaShop.Common.Models.Mapper" />
|
||||
<Using Include="NetinaShop.Core" />
|
||||
<Using Include="NetinaShop.Core.CoreServices.Abstracts" />
|
||||
<Using Include="NetinaShop.Core.Models.Api" />
|
||||
<Using Include="NetinaShop.Domain" />
|
||||
<Using Include="NetinaShop.Domain.Dtos.RequestDtos" />
|
||||
<Using Include="NetinaShop.Domain.Entities.Users" />
|
||||
<Using Include="NetinaShop.Domain.Models.Settings" />
|
||||
<Using Include="NetinaShop.Infrastructure" />
|
||||
<Using Include="NetinaShop.Infrastructure.Models" />
|
||||
<Using Include="NetinaShop.Repository" />
|
||||
<Using Include="NetinaShop.Repository.Extensions" />
|
||||
<Using Include="NetinaShop.Repository.Models" />
|
||||
<Using Include="NetinaShop.Repository.Repositories.Base.Contracts" />
|
||||
<Using Include="Carter" />
|
||||
<Using Include="Mapster" />
|
||||
<Using Include="MediatR" />
|
||||
<Using Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||
<Using Include="Microsoft.AspNetCore.Authorization" />
|
||||
<Using Include="Microsoft.AspNetCore.Identity" />
|
||||
<Using Include="Microsoft.AspNetCore.Mvc" />
|
||||
<Using Include="Microsoft.AspNetCore.Mvc.Authorization" />
|
||||
<Using Include="Microsoft.AspNetCore.Mvc.Filters" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||
<Using Include="Microsoft.Extensions.Primitives" />
|
||||
<Using Include="Microsoft.IdentityModel.Tokens" />
|
||||
<Using Include="Newtonsoft.Json" />
|
||||
<Using Include="Newtonsoft.Json.Serialization" />
|
||||
<Using Include="Serilog" />
|
||||
<Using Include="Serilog.Events" />
|
||||
<Using Include="Serilog.Sinks.ElmahIo" />
|
||||
<Using Include="Serilog.Sinks.SystemConsole.Themes" />
|
||||
<Using Include="NetinaShop.Api.WebFramework.Bases" />
|
||||
<Using Include="NetinaShop.Api.WebFramework.Configurations" />
|
||||
<Using Include="NetinaShop.Api.WebFramework.MiddleWares" />
|
||||
<Using Include="StackExchange.Redis.Extensions.Core.Configuration" />
|
||||
<Using Include="StackExchange.Redis.Extensions.Newtonsoft" />
|
||||
<Using Include="System.Diagnostics" />
|
||||
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<Using Include="System.IO.Compression" />
|
||||
<Using Include="System.Linq.Expressions" />
|
||||
<Using Include="System.Net" />
|
||||
<Using Include="System.Security.Claims" />
|
||||
<Using Include="System.Text" />
|
||||
<Using Include="NetinaShop.Common.Models;" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NetinaShop.Infrastructure\NetinaShop.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,2 @@
|
|||
@NetinaShop.Api_HostAddress = http://localhost:5173
|
||||
|
|
@ -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<SiteSettings>();
|
||||
builder.Services.Configure<SiteSettings>(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<ContainerBuilder>(builder =>
|
||||
{
|
||||
|
||||
var assembly = typeof(CoreConfig).Assembly;
|
||||
builder
|
||||
.RegisterAssemblyTypes(assembly)
|
||||
.AssignableTo<IScopedDependency>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
var assemblyB = typeof(InfrastructureConfig).Assembly;
|
||||
builder.RegisterAssemblyTypes(assemblyB)
|
||||
.AssignableTo<IScopedDependency>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
var assemblyC = typeof(RepositoryConfig).Assembly;
|
||||
builder.RegisterAssemblyTypes(assemblyC)
|
||||
.AssignableTo<IScopedDependency>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
|
||||
var assemblyD = typeof(Program).Assembly;
|
||||
builder.RegisterAssemblyTypes(assemblyD)
|
||||
.AssignableTo<IScopedDependency>()
|
||||
.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();
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<object>(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<object>(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<object>(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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
namespace NetinaShop.Api.WebFramework.Bases;
|
||||
|
||||
|
||||
public class CrudEndpoint<TEntity,TGetAllQuery,TGetOneQuery,TCreateCommand,TUpdateCommand,TDeleteCommand> 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<IResult> GetAllAsync(ISender sender , CancellationToken cancellationToken)
|
||||
{
|
||||
var res = sender.Send(Activator.CreateInstance<TGetAllQuery>(),cancellationToken);
|
||||
return TypedResults.Ok(res);
|
||||
}
|
||||
|
||||
// GET:Get An Entity By Id
|
||||
public async Task<IResult> GetAsync(Guid id, ISender sender, CancellationToken cancellationToken)
|
||||
=> TypedResults.Ok(sender.Send(Activator.CreateInstance<TGetOneQuery>()));
|
||||
|
||||
// POST:Create Entity
|
||||
public virtual async Task<IResult> Post([FromBody] TCreateCommand ent , ISender mediator , CancellationToken cancellationToken)
|
||||
{
|
||||
return TypedResults.Ok(await mediator.Send(ent, cancellationToken));
|
||||
}
|
||||
|
||||
// PUT:Update Entity
|
||||
public virtual async Task<IResult> Put([FromBody] TEntity ent , IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper.SetRepository<TEntity>().Update(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
// DELETE:Delete Entity
|
||||
public virtual async Task<IResult> Delete(Guid id,IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||
_repositoryWrapper.SetRepository<TEntity>().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<TDto, TEntity> : BaseController
|
||||
where TDto : BaseDto<TDto, TEntity>, new()
|
||||
where TEntity : ApiEntity, new()
|
||||
{
|
||||
protected readonly IRepositoryWrapper _repositoryWrapper;
|
||||
|
||||
public CrudController(IRepositoryWrapper repositoryWrapper)
|
||||
{
|
||||
_repositoryWrapper = repositoryWrapper;
|
||||
}
|
||||
|
||||
// GET:Get All Entity
|
||||
[HttpGet]
|
||||
public virtual async Task<IActionResult> GetAllAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var projectTo = typeof(TDto).BaseType?.GetProperty("ProjectToDto")?.GetValue(null, null);
|
||||
if (projectTo != null)
|
||||
{
|
||||
var exprss = projectTo as Expression<Func<TEntity, TDto>>;
|
||||
var entites = await _repositoryWrapper
|
||||
.SetRepository<TEntity>()
|
||||
.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<IActionResult> GetAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||
var dto = ent.Adapt<TDto>();
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
// POST:Add New Entity
|
||||
[HttpPost]
|
||||
public virtual async Task<IActionResult> PostOrginal([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper.SetRepository<TEntity>().Add(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok(ent);
|
||||
}
|
||||
|
||||
// POST:Add New Entity By Dto
|
||||
[HttpPost("Dto")]
|
||||
public async Task<IActionResult> PostDto([FromBody] TDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper
|
||||
.SetRepository<TEntity>()
|
||||
.Add(dto.ToEntity());
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// PUT:Update Entity
|
||||
[HttpPut]
|
||||
public virtual async Task<IActionResult> Put([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper
|
||||
.SetRepository<TEntity>()
|
||||
.Update(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// DELETE:Delete Entity
|
||||
[HttpDelete]
|
||||
[Route("{id:int}")]
|
||||
public virtual async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await _repositoryWrapper
|
||||
.SetRepository<TEntity>()
|
||||
.GetByIdAsync(cancellationToken, id);
|
||||
_repositoryWrapper.SetRepository<TEntity>().Delete(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
public class CrudController<TEntity> : BaseController
|
||||
where TEntity : ApiEntity, new()
|
||||
{
|
||||
protected readonly IRepositoryWrapper _repositoryWrapper;
|
||||
|
||||
public CrudController(IRepositoryWrapper repositoryWrapper)
|
||||
{
|
||||
_repositoryWrapper = repositoryWrapper;
|
||||
}
|
||||
|
||||
// GET:Get All Entity
|
||||
[HttpGet]
|
||||
public virtual async Task<IActionResult> GetAllAsync()
|
||||
{
|
||||
return Ok(await _repositoryWrapper.SetRepository<TEntity>().TableNoTracking.ToListAsync());
|
||||
}
|
||||
|
||||
// GET:Get An Entity By Id
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetAsync(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||
return Ok(ent);
|
||||
}
|
||||
|
||||
// POST:Add New Entity
|
||||
[HttpPost]
|
||||
public virtual async Task<IActionResult> Post([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper.SetRepository<TEntity>().Add(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok(ent);
|
||||
}
|
||||
|
||||
// PUT:Update Entity
|
||||
[HttpPut]
|
||||
public virtual async Task<IActionResult> Put([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||
{
|
||||
_repositoryWrapper.SetRepository<TEntity>().Update(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// DELETE:Delete Entity
|
||||
[HttpDelete("{id}")]
|
||||
public virtual async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||
_repositoryWrapper.SetRepository<TEntity>().Delete(ent);
|
||||
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace NetinaShop.Api.WebFramework.Configurations;
|
||||
|
||||
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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') به صورت بزرگ داشته باشد"
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
|
||||
|
||||
//load ip rules from appsettings.json
|
||||
services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));
|
||||
|
||||
// inject counter and rules stores
|
||||
//services.AddInMemoryRateLimiting();
|
||||
services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
|
||||
services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
|
||||
services.AddDistributedRateLimiting<AsyncKeyLockProcessingStrategy>();
|
||||
services.AddDistributedRateLimiting<RedisProcessingStrategy>();
|
||||
services.AddRedisRateLimiting();
|
||||
|
||||
// configuration (resolvers, counter key builders)
|
||||
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||
}
|
||||
public static void AddCustomStackExchangeRedis(this IServiceCollection serviceCollection, SiteSettings siteSettings)
|
||||
{
|
||||
serviceCollection.AddStackExchangeRedisExtensions<NewtonsoftSerializer>(options =>
|
||||
{
|
||||
return new List<RedisConfiguration>
|
||||
{
|
||||
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<ApplicationContext>(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<GzipCompressionProviderOptions>(options =>
|
||||
options.Level = CompressionLevel.Fastest);
|
||||
serviceCollection.AddResponseCompression(options =>
|
||||
{
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
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<ApplicationUser, ApplicationRole>(options =>
|
||||
{
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.User.RequireUniqueEmail = false;
|
||||
}).AddEntityFrameworkStores<ApplicationContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddErrorDescriber<PersianIdentityErrorDescriber>();
|
||||
;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
namespace NetinaShop.Api.WebFramework.MiddleWares;
|
||||
|
||||
public static class ExceptionHandlerMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
return applicationBuilder.UseMiddleware<ExceptionHandlerMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
public class ExceptionHandlerMiddleware
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public ExceptionHandlerMiddleware(
|
||||
RequestDelegate next,
|
||||
IWebHostEnvironment env,
|
||||
ILogger<ExceptionHandlerMiddleware> 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<string, string>
|
||||
{
|
||||
["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<string, string>
|
||||
{
|
||||
["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<object>(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<string, string>
|
||||
{
|
||||
["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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
namespace NetinaShop.Api.WebFramework.MiddleWares;
|
||||
|
||||
public static class PerformanceMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UsePerformanceMiddlewar(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
return applicationBuilder.UseMiddleware<PerformanceMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
public class PerformanceMiddleware
|
||||
{
|
||||
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly Stopwatch _timer;
|
||||
|
||||
public PerformanceMiddleware(
|
||||
RequestDelegate next,
|
||||
ILogger<ExceptionHandlerMiddleware> 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}");
|
||||
}
|
||||
}
|
|
@ -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<RemoveVersionParameters>();
|
||||
|
||||
////set version "api/v{version}/[controller]" from current swagger doc verion
|
||||
//options.DocumentFilter<SetVersionInPaths>();
|
||||
|
||||
////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<ApiVersionAttribute>()
|
||||
// .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<UnauthorizedResponsesOperationFilter>(true, "Bearer");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Customize
|
||||
|
||||
options.OperationFilter<ApplySummariesOperationFilter>();
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Common
|
||||
{
|
||||
public class CommonConfig
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace NetinaShop.Common.Extensions
|
||||
{
|
||||
public static class AssertExtensions
|
||||
{
|
||||
public static void NotNull<T>(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>(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>(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<object>().Any())
|
||||
throw new ArgumentException("Argument is empty : " + message, $"{name} : {typeof(T)}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using NetinaShop.Common.Models.Entity;
|
||||
|
||||
namespace NetinaShop.Common.Extensions
|
||||
{
|
||||
public static class ClassDisplayExtensions
|
||||
{
|
||||
public static string GetDisplayAttributeName<T>()
|
||||
{
|
||||
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<T>()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<T> GetEnumValues<T>(this T input) where T : struct
|
||||
{
|
||||
if (!typeof(T).IsEnum)
|
||||
throw new NotSupportedException();
|
||||
|
||||
return Enum.GetValues(input.GetType()).Cast<T>();
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetEnumFlags<T>(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<DisplayAttribute>(false).FirstOrDefault();
|
||||
|
||||
if (attribute == null)
|
||||
return value.ToString();
|
||||
|
||||
var propValue = attribute.GetType().GetProperty(property.ToString()).GetValue(attribute, null);
|
||||
return propValue.ToString();
|
||||
}
|
||||
|
||||
public static Dictionary<int, string> ToDictionary(this Enum value)
|
||||
{
|
||||
return Enum.GetValues(value.GetType()).Cast<Enum>().ToDictionary(p => Convert.ToInt32(p), q => ToDisplay(q));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<DisplayAttribute>().FirstOrDefault();
|
||||
if (attr == null) return memberInfo.Name;
|
||||
|
||||
return attr.Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace NetinaShop.Common.Extensions
|
||||
{
|
||||
public static class RandomExtensions
|
||||
{
|
||||
public static T RandomItem<T>(List<T> originList)
|
||||
{
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
var rand = random.Next(0, originList.Count - 1);
|
||||
return originList[rand];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<TUser>
|
||||
{
|
||||
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<string> Permissions { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class AccessToken<TUser,TRole>
|
||||
{
|
||||
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<string> Permissions { get; set; }
|
||||
public List<TRole> Roles { get; set; } = new();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Common.Models.Api
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public bool Seeded { get; set; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
namespace NetinaShop.Common.Models.Entity;
|
||||
public abstract class ApiEntity : IApiEntity , IEquatable<ApiEntity>
|
||||
{
|
||||
[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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace NetinaShop.Common.Models
|
||||
{
|
||||
public interface IScopedDependency
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Base Dto Class initial map config between entity and dto
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">Type of Dto Class</typeparam>
|
||||
/// <typeparam name="TEntity">Type of Entity Class</typeparam>
|
||||
public abstract class BaseDto<TDto, TEntity> : INotifyPropertyChanged, IBaseDto<TDto,TEntity> where TEntity : class where TDto : class
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public static Expression<Func<TEntity, TDto>> ProjectToDto
|
||||
{
|
||||
get => GetProjectToDto();
|
||||
}
|
||||
private static Expression<Func<TEntity, TDto>> 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<Func<TEntity, TDto>>;
|
||||
}
|
||||
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<Func<TEntity, TDto>>;
|
||||
}
|
||||
else
|
||||
throw new AppException($"{typeof(TDto).Name} Projection Not Implemented");
|
||||
}
|
||||
public virtual bool Compare(object obj)
|
||||
{
|
||||
if(obj is BaseDto<TDto,TEntity> 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<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System.Linq.Expressions;
|
||||
|
||||
namespace NetinaShop.Common.Models.Mapper
|
||||
{
|
||||
public interface IBaseDto<TDto,TEntity>
|
||||
{
|
||||
Guid Id { get; set; }
|
||||
bool Compare(object obj);
|
||||
static Expression<Func<TEntity, TDto>> ProjectToDto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
namespace NetinaShop.Common.Models.Report
|
||||
{
|
||||
public class ChartUnit
|
||||
{
|
||||
public List<long> Values { get; set; } = new();
|
||||
|
||||
public List<string> ValuesStr
|
||||
{
|
||||
get { return Values.Select(v => v.ToString()).ToList(); }
|
||||
}
|
||||
|
||||
public List<string> Labels { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ChartUnitIQuery
|
||||
{
|
||||
public IQueryable<long> Values { get; set; }
|
||||
|
||||
public List<string> ValuesStr
|
||||
{
|
||||
get { return Values.Select(v => v.ToString()).ToList(); }
|
||||
}
|
||||
|
||||
public IQueryable<string> Labels { get; set; }
|
||||
}
|
||||
|
||||
public class ChartUnit<TValue>
|
||||
{
|
||||
public List<TValue> Values { get; set; } = new();
|
||||
|
||||
public List<string> ValuesStr
|
||||
{
|
||||
get { return Values.Select(v => v.ToString()).ToList(); }
|
||||
}
|
||||
|
||||
public List<string> Labels { get; set; } = new();
|
||||
}
|
||||
}
|
|
@ -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<ReportRequestProp> ReportRequestProps { get; set; } = new();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
namespace NetinaShop.Common.Models.Report
|
||||
{
|
||||
public class ReportResult
|
||||
{
|
||||
private List<ReportRow> _rows;
|
||||
|
||||
public List<ReportRow> Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rows == null)
|
||||
_rows = new List<ReportRow>();
|
||||
return _rows;
|
||||
}
|
||||
set => _rows = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReportRow
|
||||
{
|
||||
private List<string> _cells;
|
||||
|
||||
public List<string> Cells
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cells == null)
|
||||
_cells = new List<string>();
|
||||
return _cells;
|
||||
}
|
||||
set => _cells = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MD.PersianDateTime.Standard" Version="2.5.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||
</ItemGroup>-->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MD.PersianDateTime.Standard" Version="2.5.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="MD.PersianDateTime.Standard" />
|
||||
<Using Include="Newtonsoft.Json" />
|
||||
<Using Include="Newtonsoft.Json.Serialization" />
|
||||
<Using Include="System.ComponentModel.DataAnnotations" />
|
||||
<Using Include="System.ComponentModel.DataAnnotations.Schema" />
|
||||
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Core.Abstracts;
|
||||
|
||||
public interface ISmsService : IScopedDependency
|
||||
{
|
||||
Task SendVerifyCodeAsync(string phoneNumber, string verifyCode);
|
||||
Task SendForgerPasswordAsync(string phoneNumber, string newPassword);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace NetinaShop.Core.BaseServices.Abstracts;
|
||||
|
||||
public interface IJwtService : IScopedDependency
|
||||
{
|
||||
Task<AccessToken<TUser>> Generate<TUser>(TUser user, Guid complexId, Guid roleId) where TUser : ApplicationUser;
|
||||
Task<AccessToken<TUser>> Generate<TUser>(TUser user, Guid complexId) where TUser : ApplicationUser;
|
||||
Task<AccessToken<TUser>> Generate<TUser>(TUser user) where TUser : ApplicationUser;
|
||||
|
||||
Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(TUser user, Guid complexId, Guid roleId) where TUser : ApplicationUser;
|
||||
Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(TUser user, Guid complexId) where TUser : ApplicationUser;
|
||||
Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(TUser user) where TUser : ApplicationUser;
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
namespace NetinaShop.Core.BaseServices;
|
||||
|
||||
|
||||
public class JwtService : IJwtService
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly RoleManager<ApplicationRole> _roleManager;
|
||||
private readonly SiteSettings _siteSettings;
|
||||
|
||||
public JwtService(
|
||||
IOptionsSnapshot<SiteSettings> siteSettings,
|
||||
SignInManager<ApplicationUser> userSignInManager,
|
||||
RoleManager<ApplicationRole> roleManager)
|
||||
{
|
||||
_signInManager = userSignInManager;
|
||||
_roleManager = roleManager;
|
||||
_siteSettings = siteSettings.Value;
|
||||
}
|
||||
public async Task<AccessToken<TUser>> Generate<TUser>(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<TUser>(user, claims);
|
||||
token.Permissions = claims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList();
|
||||
return token;
|
||||
}
|
||||
public async Task<AccessToken<TUser>> Generate<TUser>(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<AccessToken<TUser>> Generate<TUser>(TUser user) where TUser : ApplicationUser
|
||||
{
|
||||
var tokenId = StringExtensions.GetId(8);
|
||||
var claims = await GetClaims(user, tokenId);
|
||||
return BaseGenerate(user, claims);
|
||||
|
||||
}
|
||||
|
||||
public async Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(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<TUserDto, TUser>(user, claims);
|
||||
token.Permissions = claims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList();
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
public async Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(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<TUserDto, TUser>(user, claims);
|
||||
}
|
||||
public async Task<AccessToken<TUserDto>> Generate<TUserDto, TUser>(TUser user) where TUser : ApplicationUser
|
||||
{
|
||||
var tokenId = StringExtensions.GetId(8);
|
||||
var claims = await GetClaims(user, tokenId);
|
||||
return BaseGenerate<TUserDto, TUser>(user, claims);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private AccessToken<TUser> BaseGenerate<TUser>(TUser user, List<Claim> 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<TUser>(handler.CreateJwtSecurityToken(desctiptor));
|
||||
token.User = user;
|
||||
return token;
|
||||
}
|
||||
private AccessToken<TUserDto> BaseGenerate<TUserDto, TUser>(TUser user, List<Claim> 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<TUserDto>(handler.CreateJwtSecurityToken(desctiptor));
|
||||
token.User = user.Adapt<TUserDto>();
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
private async Task<List<Claim>> GetClaims<TUser>(TUser baseUser, string jwtId) where TUser : ApplicationUser
|
||||
{
|
||||
var clFac = (await _signInManager.ClaimsFactory.CreateAsync(baseUser));
|
||||
var claims = new List<Claim>();
|
||||
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<List<Claim>> GetClaims<TUser>(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<Claim>();
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Core
|
||||
{
|
||||
public class CoreConfig
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace NetinaShop.Core.CoreServices.Abstracts;
|
||||
|
||||
public interface IAccountService : IScopedDependency
|
||||
{
|
||||
public Task<AccessToken<ApplicationUserSDto>> LoginWithPasswordAsync(string userName, string password, CancellationToken cancellationToken);
|
||||
public Task<AccessToken<ApplicationUserSDto>> LoginWithVerifyCodeAsync(string userName, string verifyCode, CancellationToken cancellationToken);
|
||||
public Task<VerifyCodeResponseDto> GetVerifyCodeAsync(string phoneNumber);
|
||||
public Task<bool> ForgetPasswordAsync(string phoneNumber);
|
||||
public Task<bool> CheckMemberShipAsync(string phoneNumber);
|
||||
public Task<AccessToken<ApplicationUserSDto>> CompleteSignUpAsync(SignUpRequestDto requestDto, CancellationToken cancellationToken);
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
namespace NetinaShop.Core.CoreServices;
|
||||
|
||||
|
||||
public class AccountService : IAccountService
|
||||
{
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _userSignInManager;
|
||||
private readonly IJwtService _jwtService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IRepositoryWrapper _repositoryWrapper;
|
||||
private readonly ISmsService _smsService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AccountService(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> 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<bool> 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<bool> CheckMemberShipAsync(string phoneNumber)
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(phoneNumber);
|
||||
if (user == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<VerifyCodeResponseDto> 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<AccessToken<ApplicationUserSDto>> 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<AccessToken<ApplicationUserSDto>> 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<AccessToken<ApplicationUserSDto>> 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<AccessToken<ApplicationUserSDto>> CompleteLogin(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
AccessToken<ApplicationUserSDto> jwt;
|
||||
jwt = await _jwtService.Generate<ApplicationUserSDto, ApplicationUser>(user);
|
||||
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
namespace NetinaShop.Core.EntityServices.Abstracts;
|
||||
|
||||
public interface IUserService : IScopedDependency
|
||||
{
|
||||
Task<ProfileResponseDto> GetUserProfileAsync(CancellationToken cancellationToken);
|
||||
Task<List<ApplicationUserSDto>> GetUsersAsync(int page = 0, CancellationToken cancellationToken = default);
|
||||
Task<ApplicationUserSDto> GetUserAsync(Guid userId);
|
||||
Task<ApplicationUser> CreateUserAsync(string phoneNumber);
|
||||
Task<ApplicationUser> CreateUserAsync(UserActionRequestDto request, CancellationToken cancellationToken);
|
||||
Task<bool> EditUserAsync(UserActionRequestDto request, CancellationToken cancellationToken);
|
||||
Task<bool> EditUserProfileAsync(UserActionRequestDto request, CancellationToken cancellationToken);
|
||||
Task<bool> RemoveUserAsync(Guid userId, CancellationToken cancellationToken);
|
||||
|
||||
|
||||
Task<List<ApplicationRole>> GetRolesAsync(int page = 0, CancellationToken cancellationToken = default);
|
||||
Task<RoleActionRequestDto> GetRoleAsync(Guid roleId);
|
||||
Task<ApplicationRole> CreateRoleAsync(RoleActionRequestDto request);
|
||||
Task<bool> EditRoleAsync(RoleActionRequestDto request);
|
||||
Task<bool> RemoveRoleAsync(Guid roleId);
|
||||
List<ClaimDto> GetPermissions();
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
namespace NetinaShop.Core.EntityServices;
|
||||
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly RoleManager<ApplicationRole> _roleManager;
|
||||
private readonly IRepositoryWrapper _repositoryWrapper;
|
||||
private readonly IJwtService _jwtService;
|
||||
|
||||
public UserService(ICurrentUserService currentUserService,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<ApplicationRole> roleManager,
|
||||
IRepositoryWrapper repositoryWrapper,
|
||||
IJwtService jwtService)
|
||||
{
|
||||
_currentUserService = currentUserService;
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
_repositoryWrapper = repositoryWrapper;
|
||||
_jwtService = jwtService;
|
||||
}
|
||||
|
||||
|
||||
public async Task<ProfileResponseDto> 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<List<ApplicationUserSDto>> 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<ApplicationUserSDto> 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<ApplicationUser> 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<ApplicationUser> 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<bool> 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<bool> 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<bool> 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<List<ApplicationRole>> GetRolesAsync(int page = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var roles = await _roleManager.Roles
|
||||
.Skip(page * 15)
|
||||
.Take(15)
|
||||
.ToListAsync(cancellationToken);
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task<RoleActionRequestDto> GetRoleAsync(Guid roleId)
|
||||
{
|
||||
var role = (await _roleManager.FindByIdAsync(roleId.ToString()));
|
||||
if (role == null)
|
||||
throw new AppException("نقش پیدا نشد", ApiResultStatusCode.NotFound);
|
||||
|
||||
var roleDto = role.Adapt<RoleActionRequestDto>();
|
||||
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<ApplicationRole> 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<bool> 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<bool> 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<ClaimDto> GetPermissions()
|
||||
{
|
||||
return ApplicationClaims.AllClaimDtos;
|
||||
}
|
||||
}
|
|
@ -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<TData> : 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>(TData data)
|
||||
{
|
||||
return new ApiResult<TData>(true, ApiResultStatusCode.Success, data);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(OkResult result)
|
||||
{
|
||||
return new ApiResult<TData>(true, ApiResultStatusCode.Success, null);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(OkObjectResult result)
|
||||
{
|
||||
return new ApiResult<TData>(true, ApiResultStatusCode.Success, (TData)result.Value);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(BadRequestResult result)
|
||||
{
|
||||
return new ApiResult<TData>(false, ApiResultStatusCode.BadRequest, null);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(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<TData>(false, ApiResultStatusCode.BadRequest, null, message);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(ContentResult result)
|
||||
{
|
||||
return new ApiResult<TData>(true, ApiResultStatusCode.Success, null, result.Content);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(NotFoundResult result)
|
||||
{
|
||||
return new ApiResult<TData>(false, ApiResultStatusCode.NotFound, null);
|
||||
}
|
||||
|
||||
public static implicit operator ApiResult<TData>(NotFoundObjectResult result)
|
||||
{
|
||||
return new ApiResult<TData>(false, ApiResultStatusCode.NotFound, (TData)result.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||
<PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" />
|
||||
<PackageReference Include="Autofac.Extras.Quartz" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NetinaShop.Repository\NetinaShop.Repository.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Abstracts\" />
|
||||
<Folder Include="BaseServices\Abstracts\" />
|
||||
<Folder Include="CoreServices\Abstracts\" />
|
||||
<Folder Include="Models\Api\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Mapster" />
|
||||
<Using Include="Microsoft.AspNetCore.Identity" />
|
||||
<Using Include="Microsoft.AspNetCore.Mvc" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||
<Using Include="Microsoft.Extensions.Options" />
|
||||
<Using Include="Microsoft.IdentityModel.Tokens" />
|
||||
<Using Include="NetinaShop.Common.Extensions" />
|
||||
<Using Include="NetinaShop.Common.Models" />
|
||||
<Using Include="NetinaShop.Common.Models.Api" />
|
||||
<Using Include="NetinaShop.Common.Models.Exception" />
|
||||
<Using Include="NetinaShop.Core.Abstracts" />
|
||||
<Using Include="NetinaShop.Core.BaseServices.Abstracts" />
|
||||
<Using Include="NetinaShop.Core.CoreServices.Abstracts" />
|
||||
<Using Include="NetinaShop.Core.EntityServices.Abstracts" />
|
||||
<Using Include="NetinaShop.Domain.Dtos.RequestDtos" />
|
||||
<Using Include="NetinaShop.Domain.Dtos.ResponseDtos" />
|
||||
<Using Include="NetinaShop.Domain.Dtos.SmallDtos" />
|
||||
<Using Include="NetinaShop.Domain.Entities.Users" />
|
||||
<Using Include="NetinaShop.Domain.Enums" />
|
||||
<Using Include="NetinaShop.Domain.Mappers" />
|
||||
<Using Include="NetinaShop.Domain.Models.Claims" />
|
||||
<Using Include="NetinaShop.Domain.Models.Settings" />
|
||||
<Using Include="NetinaShop.Repository.Abstracts" />
|
||||
<Using Include="NetinaShop.Repository.Repositories.Base.Contracts" />
|
||||
<Using Include="Newtonsoft.Json" />
|
||||
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<Using Include="System.Security.Claims" />
|
||||
<Using Include="System.Text" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Domain
|
||||
{
|
||||
public class DomainConfig
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<string> Permissions { get; set; } = new();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<Guid> RoleIds { get; set; } = new();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace NetinaShop.Domain.Dtos.ResponseDtos;
|
||||
|
||||
public class ProfileResponseDto
|
||||
{
|
||||
public List<string> Roles { get; set; } = new();
|
||||
public ApplicationUserSDto? User { get; set; }
|
||||
public List<string> Permissions { get; set; } = new();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Domain.Dtos.ResponseDtos;
|
||||
|
||||
public class VerifyCodeResponseDto
|
||||
{
|
||||
public SignUpStatus SignUpStatus { get; set; }
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
namespace NetinaShop.Domain.Dtos.SmallDtos;
|
||||
|
||||
public class ApplicationUserSDto : BaseDto<ApplicationUserSDto, ApplicationUser>
|
||||
{
|
||||
|
||||
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<Guid> RoleIds { get; set; } = new();
|
||||
public long BirthDateTimeStamp => DateTimeExtensions.DateTimeToUnixTimeStamp(BirthDate);
|
||||
}
|
|
@ -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<BlogStorageFile> Files { get; internal set; } = new();
|
||||
|
||||
}
|
|
@ -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<Blog> Blogs { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<Product> Products { get; internal set; } = new();
|
||||
public List<BrandStorageFile> Files { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<Category> Children { get; internal set; } = new();
|
||||
public List<ProductCategory> Products { get; internal set; } = new();
|
||||
public List<CategoryStorageFile> Files { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<Order> Orders { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<OrderProduct> OrderProducts { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<Specification> Specifications { get; internal set; } = new();
|
||||
public List<Review> Reviews { get; internal set; } = new();
|
||||
public List<ProductCategory> Categories { get; internal set; } = new();
|
||||
public List<ProductStorageFile> Files { get; internal set; } = new();
|
||||
public List<OrderProduct> OrderProducts { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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<Specification> Children { get; internal set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace NetinaShop.Domain.Entities.Users;
|
||||
|
||||
public class ApplicationRole : IdentityRole<Guid>
|
||||
{
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string EnglishName { get; set; } = string.Empty;
|
||||
public string PersianName { get; set; } = string.Empty;
|
||||
}
|
|
@ -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<Guid>
|
||||
{
|
||||
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<UserAddress> Addresses { get; set; } = new();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace NetinaShop.Domain.Enums;
|
||||
|
||||
public enum DiscountAmountType
|
||||
{
|
||||
[Display(Name = "درصدی")]
|
||||
Percent,
|
||||
[Display(Name = "مبلغی")]
|
||||
Amount
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace NetinaShop.Domain.Enums;
|
||||
|
||||
public enum DiscountType
|
||||
{
|
||||
[Display(Name = "همه اقلام")]
|
||||
All,
|
||||
[Display(Name = "محصولی")]
|
||||
Product,
|
||||
[Display(Name = "دسته ای")]
|
||||
Category,
|
||||
[Display(Name = "مشترکی")]
|
||||
Subscriber
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NetinaShop.Domain.Enums;
|
||||
|
||||
public enum Gender
|
||||
{
|
||||
Male,
|
||||
Female
|
||||
}
|
|
@ -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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue