feat : config porjects
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Postgres": "User ID=postgres;Password=root;Host=localhost;Port=5432;Database=iGarsonDB;",
|
||||||
|
"PostgresServer": "Host=pg-0,pg-1;Username=igarsonAgent;Password=xHTpBf4wC+bBeNg2pL6Ga7VEWKFJx7VPEUpqxwPFfOc2YYTVwFQuHfsiqoVeT9+6;Database=BrizcoDB;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": {
|
||||||
|
"UserSetting": {
|
||||||
|
"Username": "Root",
|
||||||
|
"Email": "igarson_admin@vnfco.ir",
|
||||||
|
"Password": "root1234",
|
||||||
|
"Phone": "09211111111",
|
||||||
|
"RoleName": "RootAdmin",
|
||||||
|
"FirstName": "همه کاره",
|
||||||
|
"LastName": "سیستم"
|
||||||
|
},
|
||||||
|
"JwtSettings": {
|
||||||
|
"SecretKey": "pg8mt74/bk5yx2mr23Zvsu/81Z2czAycEo9ewcm34AndD8SFDXGqBiYv_YaHosseinYaAli_ABOOOOOOOOOLFAZL_BIMEH_JAD_NASABE_YA_GHARIBAL_GHORABA_@@@@_06/0CZWyAqy2H6Xpjp0npg8mt74/bk5yx2mr23Zvsu/81Z2czAycEo9ewcm34AndD8SFDXGqBiYvACLB0dED9vjy+h5sK1BnB30=",
|
||||||
|
"Issuer": "iGarson",
|
||||||
|
"Audience": "iGarson",
|
||||||
|
"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": "*"
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
|
||||||
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Asp.Versioning.Http" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Ben.BlockingDetector" Version="0.0.4" />
|
||||||
|
<PackageReference Include="Carter" Version="7.1.0" />
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.7.1" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.7.1" />
|
||||||
|
<PackageReference Include="MediatR.Extensions.Autofac.DependencyInjection" Version="11.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.9">
|
||||||
|
<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="4.1.16" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.8" />
|
||||||
|
<PackageReference Include="Sentry.Serilog" Version="3.34.0" />
|
||||||
|
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.PostgreSQL" Version="2.3.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.ElmahIo" Version="4.3.29" />
|
||||||
|
<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.8" />
|
||||||
|
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Autofac" />
|
||||||
|
<Using Include="Autofac.Extensions.DependencyInjection" />
|
||||||
|
<Using Include="Brizco.Common.Models.Entity" />
|
||||||
|
<Using Include="Brizco.Common.Models.Exception" />
|
||||||
|
<Using Include="Brizco.Common.Models.Mapper" />
|
||||||
|
<Using Include="Brizco.Domain.CommandQueries.Shift" />
|
||||||
|
<Using Include="Brizco.Domain.Entities" />
|
||||||
|
<Using Include="Brizco.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="Brizco.Api.WebFramework.Bases" />
|
||||||
|
<Using Include="Brizco.Api.WebFramework.Configurations" />
|
||||||
|
<Using Include="Brizco.Api.WebFramework.MiddleWares" />
|
||||||
|
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||||
|
<Using Include="System.Linq.Expressions" />
|
||||||
|
<Using Include="System.Net" />
|
||||||
|
<Using Include="System.Security.Claims" />
|
||||||
|
<Using Include="System.Text" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Berizco.Infrastructure\Brizco.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,52 @@
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Brizco.Api.Controllers;
|
||||||
|
public class ShiftController : ICarterModule
|
||||||
|
{
|
||||||
|
public ShiftController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public virtual void AddRoutes(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
var group = app.NewVersionedApi("Shift").MapGroup($"api/shift");
|
||||||
|
|
||||||
|
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 async Task<IResult> GetAllAsync(ISender sender, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(await sender.Send(Activator.CreateInstance<TGetAllQuery>(), cancellationToken));
|
||||||
|
|
||||||
|
// GET:Get An Entity By Id
|
||||||
|
public async Task<IResult> GetAsync(Guid id, ISender sender, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(await sender.Send(Activator.CreateInstance<TGetOneQuery>()));
|
||||||
|
|
||||||
|
// POST:Create Entity
|
||||||
|
public async Task<IResult> Post([FromBody] CreateShiftCommand ent, ISender mediator, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(await mediator.Send(ent, cancellationToken));
|
||||||
|
|
||||||
|
// PUT:Update Entity
|
||||||
|
public async Task<IResult> Put([FromBody] UpdateShiftCommand ent, ISender mediator, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(await mediator.Send(ent, cancellationToken));
|
||||||
|
|
||||||
|
// DELETE:Delete Entity
|
||||||
|
public async Task<IResult> Delete(Guid id, ISender mediator, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(await mediator.Send(new DeleteShiftCommand(id), cancellationToken));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Berizco.Api.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController : ControllerBase
|
|
||||||
{
|
|
||||||
public WeatherForecastController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<WeatherForecast> Get()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,95 @@
|
||||||
|
using Brizco.Api.WebFramework.Configurations;
|
||||||
|
using Brizco.Api.WebFramework.Swagger;
|
||||||
|
using Brizco.Common.Models;
|
||||||
|
using Brizco.Core;
|
||||||
|
using Brizco.Domain;
|
||||||
|
using Brizco.Domain.Models.Settings;
|
||||||
|
using Brizco.Infrastructure;
|
||||||
|
using Brizco.Repository;
|
||||||
|
using System.Configuration;
|
||||||
|
using Carter;
|
||||||
|
using FluentValidation;
|
||||||
|
using MediatR.Extensions.Autofac.DependencyInjection;
|
||||||
|
using MediatR.Extensions.Autofac.DependencyInjection.Builder;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
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.
|
// Add services to the container.
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
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.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();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
@ -17,7 +101,18 @@ if (app.Environment.IsDevelopment())
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
app.UseCors(x => x
|
||||||
|
.SetIsOriginAllowed(origin => true)
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowCredentials());
|
||||||
|
|
||||||
|
app.UseExceptionHandlerMiddleware();
|
||||||
|
|
||||||
|
app.UseCustomSwagger(siteSetting.BaseUrl);
|
||||||
|
app.MapCarter();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
await app.InitialDb();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_URLS": "http://+:80"
|
"ASPNETCORE_URLS": "http://+:80"
|
||||||
},
|
},
|
||||||
"publishAllPorts": true
|
"publishAllPorts": true,
|
||||||
|
"DockerfileRunArguments": " --network=mother -p 32769:80"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
namespace Berizco.Api
|
|
||||||
{
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
using Brizco.Common.Models.Api;
|
||||||
|
using Brizco.Core.Models.Api;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.Bases;
|
||||||
|
|
||||||
|
public class ApiResultFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public class ApiResultFilterAttribute : ActionFilterAttribute
|
||||||
|
{
|
||||||
|
public override void OnResultExecuting(ResultExecutingContext context)
|
||||||
|
{
|
||||||
|
if (context.Result is OkObjectResult okObjectResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult<object>(true, ApiResultStatusCode.Success, okObjectResult.Value);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = okObjectResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is OkResult okResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult(true, ApiResultStatusCode.Success);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = okResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is BadRequestResult badRequestResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = badRequestResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is BadRequestObjectResult badRequestObjectResult)
|
||||||
|
{
|
||||||
|
var message = badRequestObjectResult.Value.ToString();
|
||||||
|
|
||||||
|
if (badRequestObjectResult.Value is SerializableError errors)
|
||||||
|
{
|
||||||
|
var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct();
|
||||||
|
message = string.Join(" | ", errorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badRequestObjectResult.Value is ValidationProblemDetails problemDetails)
|
||||||
|
{
|
||||||
|
var errorMessages = problemDetails.Errors.Values.SelectMany(v => v);
|
||||||
|
message = string.Join(" | ", errorMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest, message);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = badRequestObjectResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is ContentResult contentResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult(true, ApiResultStatusCode.Success, contentResult.Content);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = contentResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is NotFoundResult notFoundResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult(false, ApiResultStatusCode.NotFound);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = notFoundResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is NotFoundObjectResult notFoundObjectResult)
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult<object>(false, ApiResultStatusCode.NotFound, notFoundObjectResult.Value);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = notFoundObjectResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is ObjectResult objectResult && objectResult.StatusCode == null
|
||||||
|
&& !(objectResult.Value is ApiResult))
|
||||||
|
{
|
||||||
|
var apiResult = new ApiResult<object>(true, ApiResultStatusCode.Success, objectResult.Value);
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = objectResult.StatusCode };
|
||||||
|
}
|
||||||
|
else if (context.Result is ObjectResult objectResultBad && objectResultBad.Value is ApiResult)
|
||||||
|
{
|
||||||
|
var apiResult = objectResultBad.Value as ApiResult;
|
||||||
|
context.Result = new JsonResult(apiResult) { StatusCode = objectResultBad.StatusCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnResultExecuting(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace Brizco.Api.WebFramework.Bases;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||||
|
public class ClaimRequirement : AuthorizeAttribute, IAuthorizationFilter
|
||||||
|
{
|
||||||
|
private readonly string _claimsType;
|
||||||
|
private readonly string _claimsValue;
|
||||||
|
|
||||||
|
public ClaimRequirement(string type,string value)
|
||||||
|
{
|
||||||
|
type = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAuthorization(AuthorizationFilterContext context)
|
||||||
|
{
|
||||||
|
var user = context.HttpContext.User;
|
||||||
|
var permissions = user.Claims?.Where(c => c.Type == _claimsType)?.ToList();
|
||||||
|
if (permissions == null)
|
||||||
|
{
|
||||||
|
context.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isAccepted = false;
|
||||||
|
if (permissions.FirstOrDefault(p => p.Value == _claimsValue) != null)
|
||||||
|
isAccepted = true;
|
||||||
|
if (!isAccepted)
|
||||||
|
context.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
using Carter;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.Bases;
|
||||||
|
|
||||||
|
|
||||||
|
public class CrudEndpoint<TEntity,TGetAllQuery,TGetOneQuery,TCreateCommand,TUpdateCommand,TDeleteCommand> where TEntity : ApiEntity, new()
|
||||||
|
{
|
||||||
|
private readonly string _endpointName;
|
||||||
|
|
||||||
|
public CrudEndpoint(string endpointName)
|
||||||
|
{
|
||||||
|
_endpointName = endpointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void AddRoutes(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
var group = app.NewVersionedApi(_endpointName).MapGroup($"api/{_endpointName}");
|
||||||
|
|
||||||
|
group.MapGet("", GetAllAsync)
|
||||||
|
.WithDisplayName("GetAll")
|
||||||
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
|
group.MapGet("{id}", GetAsync)
|
||||||
|
.WithName("GetOne")
|
||||||
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
|
group.MapPost("", Post)
|
||||||
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
|
group.MapPut("", Put)
|
||||||
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
|
group.MapDelete("", Delete)
|
||||||
|
.HasApiVersion(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get All Entity
|
||||||
|
public virtual async Task<IResult> GetAllAsync(ISender sender , CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var res = sender.Send(Activator.CreateInstance<TGetAllQuery>(),cancellationToken);
|
||||||
|
return TypedResults.Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get An Entity By Id
|
||||||
|
public async Task<IResult> GetAsync(Guid id, ISender sender, CancellationToken cancellationToken)
|
||||||
|
=> TypedResults.Ok(sender.Send(Activator.CreateInstance<TGetOneQuery>()));
|
||||||
|
|
||||||
|
// POST:Create Entity
|
||||||
|
public virtual async Task<IResult> Post([FromBody] TCreateCommand ent , ISender mediator , CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return TypedResults.Ok(await mediator.Send(ent, cancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT:Update Entity
|
||||||
|
public virtual async Task<IResult> Put([FromBody] TEntity ent , IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Update(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return TypedResults.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE:Delete Entity
|
||||||
|
public virtual async Task<IResult> Delete(Guid id,IRepositoryWrapper _repositoryWrapper, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Delete(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return TypedResults.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
//[AllowAnonymous]
|
||||||
|
[ApiResultFilter]
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")] // api/v1/[controller]
|
||||||
|
public class BaseController : ControllerBase
|
||||||
|
{
|
||||||
|
//public UserRepository UserRepository { get; set; } => property injection
|
||||||
|
public bool UserIsAutheticated => HttpContext.User.Identity.IsAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||||
|
public class CrudController<TDto, TEntity> : BaseController
|
||||||
|
where TDto : BaseDto<TDto, TEntity>, new()
|
||||||
|
where TEntity : ApiEntity, new()
|
||||||
|
{
|
||||||
|
protected readonly IRepositoryWrapper _repositoryWrapper;
|
||||||
|
|
||||||
|
public CrudController(IRepositoryWrapper repositoryWrapper)
|
||||||
|
{
|
||||||
|
_repositoryWrapper = repositoryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get All Entity
|
||||||
|
[HttpGet]
|
||||||
|
public virtual async Task<IActionResult> GetAllAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var projectTo = typeof(TDto).BaseType?.GetProperty("ProjectToDto")?.GetValue(null, null);
|
||||||
|
if (projectTo != null)
|
||||||
|
{
|
||||||
|
var exprss = projectTo as Expression<Func<TEntity, TDto>>;
|
||||||
|
var entites = await _repositoryWrapper
|
||||||
|
.SetRepository<TEntity>()
|
||||||
|
.TableNoTracking
|
||||||
|
.Select(exprss)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
return Ok(entites);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BaseApiException("ProjectTo Not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get An Entity By Id
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public virtual async Task<IActionResult> GetAsync(string id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||||
|
var dto = ent.Adapt<TDto>();
|
||||||
|
return Ok(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST:Add New Entity
|
||||||
|
[HttpPost]
|
||||||
|
public virtual async Task<IActionResult> PostOrginal([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Add(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST:Add New Entity By Dto
|
||||||
|
[HttpPost("Dto")]
|
||||||
|
public async Task<IActionResult> PostDto([FromBody] TDto dto, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper
|
||||||
|
.SetRepository<TEntity>()
|
||||||
|
.Add(dto.ToEntity());
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT:Update Entity
|
||||||
|
[HttpPut]
|
||||||
|
public virtual async Task<IActionResult> Put([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper
|
||||||
|
.SetRepository<TEntity>()
|
||||||
|
.Update(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE:Delete Entity
|
||||||
|
[HttpDelete]
|
||||||
|
[Route("{id:int}")]
|
||||||
|
public virtual async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ent = await _repositoryWrapper
|
||||||
|
.SetRepository<TEntity>()
|
||||||
|
.GetByIdAsync(cancellationToken, id);
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Delete(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||||
|
public class CrudController<TEntity> : BaseController
|
||||||
|
where TEntity : ApiEntity, new()
|
||||||
|
{
|
||||||
|
protected readonly IRepositoryWrapper _repositoryWrapper;
|
||||||
|
|
||||||
|
public CrudController(IRepositoryWrapper repositoryWrapper)
|
||||||
|
{
|
||||||
|
_repositoryWrapper = repositoryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get All Entity
|
||||||
|
[HttpGet]
|
||||||
|
public virtual async Task<IActionResult> GetAllAsync()
|
||||||
|
{
|
||||||
|
return Ok(await _repositoryWrapper.SetRepository<TEntity>().TableNoTracking.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET:Get An Entity By Id
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||||
|
return Ok(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST:Add New Entity
|
||||||
|
[HttpPost]
|
||||||
|
public virtual async Task<IActionResult> Post([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Add(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT:Update Entity
|
||||||
|
[HttpPut]
|
||||||
|
public virtual async Task<IActionResult> Put([FromBody] TEntity ent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Update(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE:Delete Entity
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public virtual async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ent = await _repositoryWrapper.SetRepository<TEntity>().GetByIdAsync(cancellationToken, id);
|
||||||
|
_repositoryWrapper.SetRepository<TEntity>().Delete(ent);
|
||||||
|
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.Configurations;
|
||||||
|
|
||||||
|
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
|
||||||
|
{
|
||||||
|
public void PostConfigure(string name, JwtBearerOptions options)
|
||||||
|
{
|
||||||
|
var originalOnMessageReceived = options.Events.OnMessageReceived;
|
||||||
|
options.Events.OnMessageReceived = async context =>
|
||||||
|
{
|
||||||
|
await originalOnMessageReceived(context);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(context.Token))
|
||||||
|
{
|
||||||
|
var accessToken = context.Request.Query["access_token"];
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(accessToken)) context.Token = accessToken;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Brizco.Infrastructure.Models;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.Configurations;
|
||||||
|
|
||||||
|
public static class LoggerConfig
|
||||||
|
{
|
||||||
|
public static void ConfigureSerilog()
|
||||||
|
{
|
||||||
|
var logName = $"{DirectoryAddress.Logs}/Log_Server_.log";
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(theme: AnsiConsoleTheme.Literate)
|
||||||
|
.WriteTo.Sentry(o =>
|
||||||
|
{
|
||||||
|
o.MinimumEventLevel = LogEventLevel.Error;
|
||||||
|
o.Dsn = "https://592b7fbb29464442a8e996247abe857f@watcher.igarson.app/7";
|
||||||
|
})
|
||||||
|
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
namespace Brizco.Api.WebFramework.Configurations;
|
||||||
|
|
||||||
|
public class PersianIdentityErrorDescriber : IdentityErrorDescriber
|
||||||
|
{
|
||||||
|
public override IdentityError DefaultError()
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(DefaultError), Description = "ارور ناشناخته ای رخ داده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError ConcurrencyFailure()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(ConcurrencyFailure), Description = "در درخواست شما تداخلی ایجاد شده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordMismatch()
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(PasswordMismatch), Description = "رمز عبور اشتباه است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError InvalidToken()
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(InvalidToken), Description = "توکن ارسالی اشتباه است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError LoginAlreadyAssociated()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(LoginAlreadyAssociated), Description = "یوزری با این مشخصات در حال حاضر لاگین کرده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError InvalidUserName(string userName)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(InvalidUserName),
|
||||||
|
Description = $"یوزر نیم '{userName}' صحیح نمی باشد فقط می توانید از حروف و اعداد استفاده کنید"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError InvalidEmail(string email)
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(InvalidEmail), Description = $"ایمیل '{email}' صحیح نمی باشد" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError DuplicateUserName(string userName)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(DuplicateUserName),
|
||||||
|
Description = $"یوزرنیم '{userName}' قبلا توسط اکانت دیگری استفاده شده است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError DuplicateEmail(string email)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(DuplicateEmail), Description = $"ایمیل '{email}' قبل استفاده شده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError InvalidRoleName(string role)
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(InvalidRoleName), Description = $"نقش '{role}' موجود نمی باشد" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError DuplicateRoleName(string role)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(DuplicateRoleName), Description = $"نقش '{role}' قبلا برای این کاربر استفاده شده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError UserAlreadyHasPassword()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(UserAlreadyHasPassword), Description = "کاربر قبلا رمز عبوری را استفاده کرده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError UserLockoutNotEnabled()
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "کاربر مورد نظر قفل شده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError UserAlreadyInRole(string role)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(UserAlreadyInRole), Description = "نشق مورد نظر برای این کاربر استفاده شده است" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError UserNotInRole(string role)
|
||||||
|
{
|
||||||
|
return new IdentityError { Code = nameof(UserNotInRole), Description = $"کاربر مورد نظر در نقش '{role}' نیست" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordTooShort(int length)
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{ Code = nameof(PasswordTooShort), Description = $"پسورد حداقل باید {length} کاراکتر باشد" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordRequiresNonAlphanumeric()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(PasswordRequiresNonAlphanumeric),
|
||||||
|
Description = "رمز عبور باید حداقل یک کاراکتر غیر عددی داشته باشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordRequiresDigit()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(PasswordRequiresDigit), Description = "پسور مورد نظر باید حداقل یک عدد داشته باشد ('0'-'9')"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordRequiresLower()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(PasswordRequiresLower),
|
||||||
|
Description = "پسورد مورد نظر باید حداقل یکی از حروف ('a'-'z') به صورت کوچک داشته باشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IdentityError PasswordRequiresUpper()
|
||||||
|
{
|
||||||
|
return new IdentityError
|
||||||
|
{
|
||||||
|
Code = nameof(PasswordRequiresUpper),
|
||||||
|
Description = "پسورد مورد نظر باید حداقل یکی از حروف ('A'-'Z') به صورت بزرگ داشته باشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
using System.IO.Compression;
|
||||||
|
using Asp.Versioning;
|
||||||
|
using AspNetCoreRateLimit;
|
||||||
|
using AspNetCoreRateLimit.Redis;
|
||||||
|
using Brizco.Domain.Entities.User;
|
||||||
|
using Brizco.Domain.Models.Settings;
|
||||||
|
using Brizco.Repository.Extensions;
|
||||||
|
using Brizco.Repository.Models;
|
||||||
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
|
using StackExchange.Redis.Extensions.Core.Configuration;
|
||||||
|
using StackExchange.Redis.Extensions.Newtonsoft;
|
||||||
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
|
namespace Brizco.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("PostgresServer"), b => b.MigrationsAssembly("Brizco.Repository"))
|
||||||
|
.UseProjectAssembly(typeof(ApplicationUser).Assembly);
|
||||||
|
//options.EnableServiceProviderCaching(true);
|
||||||
|
}).BuildServiceProvider();
|
||||||
|
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomResponseCompression(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.Configure<GzipCompressionProviderOptions>(options =>
|
||||||
|
options.Level = CompressionLevel.Fastest);
|
||||||
|
serviceCollection.AddResponseCompression(options =>
|
||||||
|
{
|
||||||
|
options.Providers.Add<GzipCompressionProvider>();
|
||||||
|
options.EnableForHttps = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomCores(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddCors(options => options.AddPolicy("CorsPolicy",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder.AllowAnyMethod()
|
||||||
|
.SetPreflightMaxAge(TimeSpan.FromHours(24))
|
||||||
|
.WithExposedHeaders("Access-control-allow-origins")
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.SetIsOriginAllowed(_ => true)
|
||||||
|
.AllowCredentials();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomController(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddControllers(options => { options.Filters.Add(new AuthorizeFilter()); })
|
||||||
|
.AddControllersAsServices()
|
||||||
|
.AddNewtonsoftJson(options =>
|
||||||
|
{
|
||||||
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
|
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomMvc(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection
|
||||||
|
.AddMvc()
|
||||||
|
.AddNewtonsoftJson(options =>
|
||||||
|
{
|
||||||
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomAuthorization(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddAuthorization();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddJwtCustomAuthentication(this IServiceCollection serviceCollection, JwtSettings jwtSettings)
|
||||||
|
{
|
||||||
|
serviceCollection.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
var secretKey = Encoding.UTF8.GetBytes(jwtSettings.SecretKey);
|
||||||
|
var validateParammetrs = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ClockSkew = TimeSpan.Zero,
|
||||||
|
RequireSignedTokens = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(secretKey),
|
||||||
|
RequireExpirationTime = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidAudience = jwtSettings.Audience,
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidIssuer = jwtSettings.Issuer
|
||||||
|
};
|
||||||
|
options.RequireHttpsMetadata = true;
|
||||||
|
options.SaveToken = true;
|
||||||
|
options.TokenValidationParameters = validateParammetrs;
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
|
{
|
||||||
|
var accessToken = context.Request.Query["access_token"];
|
||||||
|
|
||||||
|
// If the request is for our hub...
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
// Read the token out of the query string
|
||||||
|
context.Token = accessToken.ToString();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomIdentity(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddIdentity<ApplicationUser, ApplicationRole>(options =>
|
||||||
|
{
|
||||||
|
options.Password.RequireLowercase = false;
|
||||||
|
options.Password.RequireUppercase = false;
|
||||||
|
options.Password.RequireDigit = false;
|
||||||
|
options.Password.RequireNonAlphanumeric = false;
|
||||||
|
options.User.RequireUniqueEmail = false;
|
||||||
|
}).AddEntityFrameworkStores<ApplicationContext>()
|
||||||
|
.AddDefaultTokenProviders()
|
||||||
|
.AddErrorDescriber<PersianIdentityErrorDescriber>();
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCustomApiVersioning(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddApiVersioning(options =>
|
||||||
|
{
|
||||||
|
options.AssumeDefaultVersionWhenUnspecified = true;
|
||||||
|
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||||
|
options.ApiVersionReader = new HeaderApiVersionReader("api-version");
|
||||||
|
options.ReportApiVersions = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
using Brizco.Common.Models.Api;
|
||||||
|
using Brizco.Core.Models.Api;
|
||||||
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.MiddleWares;
|
||||||
|
|
||||||
|
public static class ExceptionHandlerMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder applicationBuilder)
|
||||||
|
{
|
||||||
|
return applicationBuilder.UseMiddleware<ExceptionHandlerMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExceptionHandlerMiddleware
|
||||||
|
{
|
||||||
|
private readonly IWebHostEnvironment _env;
|
||||||
|
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public ExceptionHandlerMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
IWebHostEnvironment env,
|
||||||
|
ILogger<ExceptionHandlerMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_env = env;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
string message = null;
|
||||||
|
var httpStatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
var apiStatusCode = ApiResultStatusCode.ServerError;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
catch (BaseApiException exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, exception.Message);
|
||||||
|
httpStatusCode = exception.HttpStatusCode;
|
||||||
|
apiStatusCode = exception.ApiStatusCode;
|
||||||
|
|
||||||
|
if (_env.IsDevelopment())
|
||||||
|
{
|
||||||
|
var dic = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Exception"] = exception.Message,
|
||||||
|
["StackTrace"] = exception.StackTrace
|
||||||
|
};
|
||||||
|
if (exception.InnerException != null)
|
||||||
|
{
|
||||||
|
dic.Add("InnerException.Exception", exception.InnerException.Message);
|
||||||
|
dic.Add("InnerException.StackTrace", exception.InnerException.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.AdditionalData != null)
|
||||||
|
dic.Add("AdditionalData", JsonConvert.SerializeObject(exception.AdditionalData));
|
||||||
|
var contractResolver = new DefaultContractResolver
|
||||||
|
{
|
||||||
|
NamingStrategy = new CamelCaseNamingStrategy()
|
||||||
|
};
|
||||||
|
|
||||||
|
message = JsonConvert.SerializeObject(dic, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ContractResolver = contractResolver,
|
||||||
|
Formatting = Formatting.Indented
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = exception.Message;
|
||||||
|
}
|
||||||
|
if(exception.AdditionalData==null)
|
||||||
|
await WriteToResponseAsync();
|
||||||
|
else
|
||||||
|
await WriteToResponseWithObjectAsync(exception.AdditionalData);
|
||||||
|
}
|
||||||
|
catch (SecurityTokenExpiredException exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, exception.Message);
|
||||||
|
SetUnAuthorizeResponse(exception);
|
||||||
|
await WriteToResponseAsync();
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, exception.Message);
|
||||||
|
SetUnAuthorizeResponse(exception);
|
||||||
|
await WriteToResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, exception.Message);
|
||||||
|
|
||||||
|
if (_env.IsDevelopment())
|
||||||
|
{
|
||||||
|
var dic = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Exception"] = exception.Message,
|
||||||
|
["InnerException"] = exception.InnerException?.Message,
|
||||||
|
["StackTrace"] = exception.StackTrace
|
||||||
|
};
|
||||||
|
message = JsonConvert.SerializeObject(dic);
|
||||||
|
}
|
||||||
|
|
||||||
|
message = exception.Message;
|
||||||
|
await WriteToResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task WriteToResponseAsync()
|
||||||
|
{
|
||||||
|
if (context.Response.HasStarted)
|
||||||
|
throw new InvalidOperationException("The response has already started, the http status code middleware will not be executed.");
|
||||||
|
|
||||||
|
var result = new ApiResult(false, apiStatusCode, message);
|
||||||
|
|
||||||
|
var contractResolver = new DefaultContractResolver
|
||||||
|
{
|
||||||
|
NamingStrategy = new CamelCaseNamingStrategy()
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ContractResolver = contractResolver,
|
||||||
|
Formatting = Formatting.Indented
|
||||||
|
});
|
||||||
|
|
||||||
|
context.Response.StatusCode = (int)httpStatusCode;
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
await context.Response.WriteAsync(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async Task WriteToResponseWithObjectAsync(object additionalData)
|
||||||
|
{
|
||||||
|
if (context.Response.HasStarted)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"The response has already started, the http status code middleware will not be executed.");
|
||||||
|
|
||||||
|
var result = new ApiResult<object>(false, apiStatusCode, additionalData, message);
|
||||||
|
|
||||||
|
var contractResolver = new DefaultContractResolver
|
||||||
|
{
|
||||||
|
NamingStrategy = new CamelCaseNamingStrategy()
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ContractResolver = contractResolver,
|
||||||
|
Formatting = Formatting.Indented
|
||||||
|
});
|
||||||
|
|
||||||
|
context.Response.StatusCode = (int)httpStatusCode;
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
await context.Response.WriteAsync(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUnAuthorizeResponse(Exception exception)
|
||||||
|
{
|
||||||
|
httpStatusCode = HttpStatusCode.Unauthorized;
|
||||||
|
apiStatusCode = ApiResultStatusCode.UnAuthorized;
|
||||||
|
|
||||||
|
if (_env.IsDevelopment())
|
||||||
|
{
|
||||||
|
var dic = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Exception"] = exception.Message,
|
||||||
|
["StackTrace"] = exception.StackTrace
|
||||||
|
};
|
||||||
|
if (exception is SecurityTokenExpiredException tokenException)
|
||||||
|
dic.Add("Expires", tokenException.Expires.ToString());
|
||||||
|
|
||||||
|
message = JsonConvert.SerializeObject(dic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JwtSecurityToken ReadJwtToken(bool fromHeader = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fromHeader)
|
||||||
|
{
|
||||||
|
var stream = context.Request.Headers.Values.First(v => v.FirstOrDefault().Contains("Bearer"))
|
||||||
|
.FirstOrDefault();
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jsonToken = handler.ReadToken(stream.Split(" ").Last());
|
||||||
|
return jsonToken as JwtSecurityToken;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string stream = context.Request.Query["access_token"];
|
||||||
|
;
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jsonToken = handler.ReadToken(stream.Split(" ").Last());
|
||||||
|
return jsonToken as JwtSecurityToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new BaseApiException(ApiResultStatusCode.UnAuthorized, e.Message + " Jwt is wrong",
|
||||||
|
HttpStatusCode.Unauthorized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Brizco.Api.WebFramework.MiddleWares;
|
||||||
|
|
||||||
|
public static class PerformanceMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UsePerformanceMiddlewar(this IApplicationBuilder applicationBuilder)
|
||||||
|
{
|
||||||
|
return applicationBuilder.UseMiddleware<PerformanceMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PerformanceMiddleware
|
||||||
|
{
|
||||||
|
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly Stopwatch _timer;
|
||||||
|
|
||||||
|
public PerformanceMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
ILogger<ExceptionHandlerMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
_timer = new Stopwatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async System.Threading.Tasks.Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
_timer.Start();
|
||||||
|
await _next(context);
|
||||||
|
_timer.Stop();
|
||||||
|
|
||||||
|
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
|
||||||
|
_logger.LogWarning($"REQUEST TIMER : {elapsedMilliseconds}");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
using Asp.Versioning;
|
||||||
|
using Brizco.Common.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Pluralize.NET;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||||
|
|
||||||
|
namespace Brizco.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/v1/user/LoginSwagger";
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 1002 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 85 KiB |
|
@ -0,0 +1,257 @@
|
||||||
|
/**
|
||||||
|
* Template Name: Arsha - v4.9.1
|
||||||
|
* Template URL: https://bootstrapmade.com/arsha-free-bootstrap-html-template-corporate/
|
||||||
|
* Author: BootstrapMade.com
|
||||||
|
* License: https://bootstrapmade.com/license/
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Easy selector helper function
|
||||||
|
*/
|
||||||
|
const select = (el, all = false) => {
|
||||||
|
el = el.trim()
|
||||||
|
if (all) {
|
||||||
|
return [...document.querySelectorAll(el)]
|
||||||
|
} else {
|
||||||
|
return document.querySelector(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Easy event listener function
|
||||||
|
*/
|
||||||
|
const on = (type, el, listener, all = false) => {
|
||||||
|
let selectEl = select(el, all)
|
||||||
|
if (selectEl) {
|
||||||
|
if (all) {
|
||||||
|
selectEl.forEach(e => e.addEventListener(type, listener))
|
||||||
|
} else {
|
||||||
|
selectEl.addEventListener(type, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Easy on scroll event listener
|
||||||
|
*/
|
||||||
|
const onscroll = (el, listener) => {
|
||||||
|
el.addEventListener('scroll', listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navbar links active state on scroll
|
||||||
|
*/
|
||||||
|
let navbarlinks = select('#navbar .scrollto', true)
|
||||||
|
const navbarlinksActive = () => {
|
||||||
|
let position = window.scrollY + 200
|
||||||
|
navbarlinks.forEach(navbarlink => {
|
||||||
|
if (!navbarlink.hash) return
|
||||||
|
let section = select(navbarlink.hash)
|
||||||
|
if (!section) return
|
||||||
|
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
|
||||||
|
navbarlink.classList.add('active')
|
||||||
|
} else {
|
||||||
|
navbarlink.classList.remove('active')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
window.addEventListener('load', navbarlinksActive)
|
||||||
|
onscroll(document, navbarlinksActive)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls to an element with header offset
|
||||||
|
*/
|
||||||
|
const scrollto = (el) => {
|
||||||
|
let header = select('#header')
|
||||||
|
let offset = header.offsetHeight
|
||||||
|
|
||||||
|
let elementPos = select(el).offsetTop
|
||||||
|
window.scrollTo({
|
||||||
|
top: elementPos - offset,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle .header-scrolled class to #header when page is scrolled
|
||||||
|
*/
|
||||||
|
let selectHeader = select('#header')
|
||||||
|
if (selectHeader) {
|
||||||
|
const headerScrolled = () => {
|
||||||
|
if (window.scrollY > 100) {
|
||||||
|
selectHeader.classList.add('header-scrolled')
|
||||||
|
} else {
|
||||||
|
selectHeader.classList.remove('header-scrolled')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('load', headerScrolled)
|
||||||
|
onscroll(document, headerScrolled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back to top button
|
||||||
|
*/
|
||||||
|
let backtotop = select('.back-to-top')
|
||||||
|
if (backtotop) {
|
||||||
|
const toggleBacktotop = () => {
|
||||||
|
if (window.scrollY > 100) {
|
||||||
|
backtotop.classList.add('active')
|
||||||
|
} else {
|
||||||
|
backtotop.classList.remove('active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('load', toggleBacktotop)
|
||||||
|
onscroll(document, toggleBacktotop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile nav toggle
|
||||||
|
*/
|
||||||
|
on('click', '.mobile-nav-toggle', function(e) {
|
||||||
|
select('#navbar').classList.toggle('navbar-mobile')
|
||||||
|
this.classList.toggle('bi-list')
|
||||||
|
this.classList.toggle('bi-x')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile nav dropdowns activate
|
||||||
|
*/
|
||||||
|
on('click', '.navbar .dropdown > a', function(e) {
|
||||||
|
if (select('#navbar').classList.contains('navbar-mobile')) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.nextElementSibling.classList.toggle('dropdown-active')
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrool with ofset on links with a class name .scrollto
|
||||||
|
*/
|
||||||
|
on('click', '.scrollto', function(e) {
|
||||||
|
if (select(this.hash)) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
let navbar = select('#navbar')
|
||||||
|
if (navbar.classList.contains('navbar-mobile')) {
|
||||||
|
navbar.classList.remove('navbar-mobile')
|
||||||
|
let navbarToggle = select('.mobile-nav-toggle')
|
||||||
|
navbarToggle.classList.toggle('bi-list')
|
||||||
|
navbarToggle.classList.toggle('bi-x')
|
||||||
|
}
|
||||||
|
scrollto(this.hash)
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll with ofset on page load with hash links in the url
|
||||||
|
*/
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
if (window.location.hash) {
|
||||||
|
if (select(window.location.hash)) {
|
||||||
|
scrollto(window.location.hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preloader
|
||||||
|
*/
|
||||||
|
let preloader = select('#preloader');
|
||||||
|
if (preloader) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
preloader.remove()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate glightbox
|
||||||
|
*/
|
||||||
|
const glightbox = GLightbox({
|
||||||
|
selector: '.glightbox'
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skills animation
|
||||||
|
*/
|
||||||
|
let skilsContent = select('.skills-content');
|
||||||
|
if (skilsContent) {
|
||||||
|
new Waypoint({
|
||||||
|
element: skilsContent,
|
||||||
|
offset: '80%',
|
||||||
|
handler: function(direction) {
|
||||||
|
let progress = select('.progress .progress-bar', true);
|
||||||
|
progress.forEach((el) => {
|
||||||
|
el.style.width = el.getAttribute('aria-valuenow') + '%'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Porfolio isotope and filter
|
||||||
|
*/
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
let portfolioContainer = select('.portfolio-container');
|
||||||
|
if (portfolioContainer) {
|
||||||
|
let portfolioIsotope = new Isotope(portfolioContainer, {
|
||||||
|
itemSelector: '.portfolio-item'
|
||||||
|
});
|
||||||
|
|
||||||
|
let portfolioFilters = select('#portfolio-flters li', true);
|
||||||
|
|
||||||
|
on('click', '#portfolio-flters li', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
portfolioFilters.forEach(function(el) {
|
||||||
|
el.classList.remove('filter-active');
|
||||||
|
});
|
||||||
|
this.classList.add('filter-active');
|
||||||
|
|
||||||
|
portfolioIsotope.arrange({
|
||||||
|
filter: this.getAttribute('data-filter')
|
||||||
|
});
|
||||||
|
portfolioIsotope.on('arrangeComplete', function() {
|
||||||
|
AOS.refresh()
|
||||||
|
});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate portfolio lightbox
|
||||||
|
*/
|
||||||
|
const portfolioLightbox = GLightbox({
|
||||||
|
selector: '.portfolio-lightbox'
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Portfolio details slider
|
||||||
|
*/
|
||||||
|
new Swiper('.portfolio-details-slider', {
|
||||||
|
speed: 400,
|
||||||
|
loop: true,
|
||||||
|
autoplay: {
|
||||||
|
delay: 5000,
|
||||||
|
disableOnInteraction: false
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
el: '.swiper-pagination',
|
||||||
|
type: 'bullets',
|
||||||
|
clickable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation on scroll
|
||||||
|
*/
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
AOS.init({
|
||||||
|
duration: 1000,
|
||||||
|
easing: "ease-in-out",
|
||||||
|
once: true,
|
||||||
|
mirror: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})()
|
|
@ -0,0 +1 @@
|
||||||
|
The .scss (Sass) files are only avilable in the pro version. You can buy it from: https://bootstrapmade.com/arsha-free-bootstrap-html-template-corporate/
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div class="sidebar">
|
||||||
|
<a class="active" href="#home">Home</a>
|
||||||
|
<a href="#news">News</a>
|
||||||
|
<a href="#contact">Contact</a>
|
||||||
|
<a href="#about">About</a>
|
||||||
|
</div>
|
|
@ -0,0 +1,488 @@
|
||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.2.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2022 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2022 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-black: #000;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
--bs-border-width: 1px;
|
||||||
|
--bs-border-style: solid;
|
||||||
|
--bs-border-color: #dee2e6;
|
||||||
|
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-border-radius: 0.375rem;
|
||||||
|
--bs-border-radius-sm: 0.25rem;
|
||||||
|
--bs-border-radius-lg: 0.5rem;
|
||||||
|
--bs-border-radius-xl: 1rem;
|
||||||
|
--bs-border-radius-2xl: 2rem;
|
||||||
|
--bs-border-radius-pill: 50rem;
|
||||||
|
--bs-link-color: #0d6efd;
|
||||||
|
--bs-link-hover-color: #0a58ca;
|
||||||
|
--bs-code-color: #d63384;
|
||||||
|
--bs-highlight-bg: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.1875em;
|
||||||
|
background-color: var(--bs-highlight-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--bs-link-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--bs-link-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-code-color);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.1875rem 0.375rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-body-bg);
|
||||||
|
background-color: var(--bs-body-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rtl:raw:
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
|
@ -0,0 +1,485 @@
|
||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.2.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2022 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2022 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-black: #000;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
--bs-border-width: 1px;
|
||||||
|
--bs-border-style: solid;
|
||||||
|
--bs-border-color: #dee2e6;
|
||||||
|
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||||
|
--bs-border-radius: 0.375rem;
|
||||||
|
--bs-border-radius-sm: 0.25rem;
|
||||||
|
--bs-border-radius-lg: 0.5rem;
|
||||||
|
--bs-border-radius-xl: 1rem;
|
||||||
|
--bs-border-radius-2xl: 2rem;
|
||||||
|
--bs-border-radius-pill: 50rem;
|
||||||
|
--bs-link-color: #0d6efd;
|
||||||
|
--bs-link-hover-color: #0a58ca;
|
||||||
|
--bs-code-color: #d63384;
|
||||||
|
--bs-highlight-bg: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.1875em;
|
||||||
|
background-color: var(--bs-highlight-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--bs-link-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--bs-link-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-code-color);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.1875rem 0.375rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--bs-body-bg);
|
||||||
|
background-color: var(--bs-body-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
|
@ -0,0 +1,386 @@
|
||||||
|
@-webkit-keyframes spin
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100%
|
||||||
|
{
|
||||||
|
-webkit-transform: rotate(359deg);
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes spin
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100%
|
||||||
|
{
|
||||||
|
-webkit-transform: rotate(359deg);
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes burst
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale(1.5);
|
||||||
|
transform: scale(1.5);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes burst
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale(1.5);
|
||||||
|
transform: scale(1.5);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes flashing
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
45%
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes flashing
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
45%
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes fade-left
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(-20px);
|
||||||
|
transform: translateX(-20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fade-left
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(-20px);
|
||||||
|
transform: translateX(-20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes fade-right
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(20px);
|
||||||
|
transform: translateX(20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fade-right
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
transform: translateX(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateX(20px);
|
||||||
|
transform: translateX(20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes fade-up
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(-20px);
|
||||||
|
transform: translateY(-20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fade-up
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(-20px);
|
||||||
|
transform: translateY(-20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes fade-down
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(20px);
|
||||||
|
transform: translateY(20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fade-down
|
||||||
|
{
|
||||||
|
0%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
75%
|
||||||
|
{
|
||||||
|
-webkit-transform: translateY(20px);
|
||||||
|
transform: translateY(20px);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes tada
|
||||||
|
{
|
||||||
|
from
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1);
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
10%,
|
||||||
|
20%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
|
||||||
|
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30%,
|
||||||
|
50%,
|
||||||
|
70%,
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
|
||||||
|
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%,
|
||||||
|
60%,
|
||||||
|
80%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
|
||||||
|
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1);
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tada
|
||||||
|
{
|
||||||
|
from
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1);
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
10%,
|
||||||
|
20%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
|
||||||
|
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30%,
|
||||||
|
50%,
|
||||||
|
70%,
|
||||||
|
90%
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
|
||||||
|
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%,
|
||||||
|
60%,
|
||||||
|
80%
|
||||||
|
{
|
||||||
|
-webkit-transform: rotate3d(0, 0, 1, -10deg);
|
||||||
|
transform: rotate3d(0, 0, 1, -10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to
|
||||||
|
{
|
||||||
|
-webkit-transform: scale3d(1, 1, 1);
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bx-spin
|
||||||
|
{
|
||||||
|
-webkit-animation: spin 2s linear infinite;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
.bx-spin-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: spin 2s linear infinite;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-tada
|
||||||
|
{
|
||||||
|
-webkit-animation: tada 1.5s ease infinite;
|
||||||
|
animation: tada 1.5s ease infinite;
|
||||||
|
}
|
||||||
|
.bx-tada-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: tada 1.5s ease infinite;
|
||||||
|
animation: tada 1.5s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-flashing
|
||||||
|
{
|
||||||
|
-webkit-animation: flashing 1.5s infinite linear;
|
||||||
|
animation: flashing 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-flashing-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: flashing 1.5s infinite linear;
|
||||||
|
animation: flashing 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-burst
|
||||||
|
{
|
||||||
|
-webkit-animation: burst 1.5s infinite linear;
|
||||||
|
animation: burst 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-burst-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: burst 1.5s infinite linear;
|
||||||
|
animation: burst 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-up
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-up 1.5s infinite linear;
|
||||||
|
animation: fade-up 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-up-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-up 1.5s infinite linear;
|
||||||
|
animation: fade-up 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-down
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-down 1.5s infinite linear;
|
||||||
|
animation: fade-down 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-down-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-down 1.5s infinite linear;
|
||||||
|
animation: fade-down 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-left
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-left 1.5s infinite linear;
|
||||||
|
animation: fade-left 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-left-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-left 1.5s infinite linear;
|
||||||
|
animation: fade-left 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-right
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-right 1.5s infinite linear;
|
||||||
|
animation: fade-right 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
.bx-fade-right-hover:hover
|
||||||
|
{
|
||||||
|
-webkit-animation: fade-right 1.5s infinite linear;
|
||||||
|
animation: fade-right 1.5s infinite linear;
|
||||||
|
}
|