Add project files.

release
Amir Hossein Khademi 2023-12-16 20:25:12 +03:30
parent 21279851fe
commit ed4410d191
138 changed files with 10109 additions and 0 deletions

View File

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"mapster.tool": {
"version": "8.4.0",
"commands": [
"dotnet-mapster"
]
}
}
}

30
.dockerignore 100644
View File

@ -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/**

View File

@ -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": "*"
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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": "*"
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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"]

View File

@ -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>

View File

@ -0,0 +1,2 @@
@NetinaShop.Api_HostAddress = http://localhost:5173

View File

@ -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();

View File

@ -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
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
};
}
}

View File

@ -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();
}
}

View File

@ -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') به صورت بزرگ داشته باشد"
};
}
}

View File

@ -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;
});
}
}

View File

@ -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);
}
}
}
}

View File

@ -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}");
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Common
{
public class CommonConfig
{
}
}

View File

@ -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)}");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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
};
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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];
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Common.Models.Api
{
public class AppSettings
{
public bool Seeded { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,6 @@
namespace NetinaShop.Common.Models
{
public interface IScopedDependency
{
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Core.Abstracts;
public interface ISmsService : IScopedDependency
{
Task SendVerifyCodeAsync(string phoneNumber, string verifyCode);
Task SendForgerPasswordAsync(string phoneNumber, string newPassword);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Core
{
public class CoreConfig
{
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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>

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Domain
{
public class DomainConfig
{
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Domain.Dtos.ResponseDtos;
public class VerifyCodeResponseDto
{
public SignUpStatus SignUpStatus { get; set; }
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -0,0 +1,9 @@
namespace NetinaShop.Domain.Enums;
public enum DiscountAmountType
{
[Display(Name = "درصدی")]
Percent,
[Display(Name = "مبلغی")]
Amount
}

View File

@ -0,0 +1,13 @@
namespace NetinaShop.Domain.Enums;
public enum DiscountType
{
[Display(Name = "همه اقلام")]
All,
[Display(Name = "محصولی")]
Product,
[Display(Name = "دسته ای")]
Category,
[Display(Name = "مشترکی")]
Subscriber
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Domain.Enums;
public enum Gender
{
Male,
Female
}

View File

@ -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