Compare commits
No commits in common. "8e58c835267a5cf5292360e4c4353b3db33b17e4" and "054d319f7ce930c1cd0aa0f088af1738eb94297c" have entirely different histories.
8e58c83526
...
054d319f7c
4
.gitignore
vendored
4
.gitignore
vendored
@ -360,6 +360,4 @@ MigrationBackup/
|
|||||||
.ionide/
|
.ionide/
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
# Fody - auto-generated XML schema
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
/ApiDto/ApiDtoDocs.xml
|
|
||||||
/Endpoint/docs.xml
|
|
@ -50,8 +50,8 @@ public class GroupScheduleInfo
|
|||||||
/// If is <see langword="null"/>, then there are no specific <see cref="Weeks"/>
|
/// If is <see langword="null"/>, then there are no specific <see cref="Weeks"/>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
///
|
///
|
||||||
public bool? IsExcludedWeeks { get; set; }
|
|
||||||
|
|
||||||
|
public bool? IsExcludedWeeks { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The week numbers required for the correct display of the schedule.
|
/// The week numbers required for the correct display of the schedule.
|
||||||
/// <br/>
|
/// <br/>
|
||||||
|
6
Endpoint/Backend.http
Normal file
6
Endpoint/Backend.http
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@Backend_HostAddress = http://localhost:5269
|
||||||
|
|
||||||
|
GET {{Backend_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Common.Attributes;
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Parameter)]
|
|
||||||
public class SwaggerDefaultAttribute(string value) : Attribute
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Versioning;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class ApiVersioningConfiguration
|
|
||||||
{
|
|
||||||
public static void AddCustomApiVersioning(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddApiVersioning(options =>
|
|
||||||
{
|
|
||||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
|
||||||
options.AssumeDefaultVersionWhenUnspecified = true;
|
|
||||||
options.ReportApiVersions = true;
|
|
||||||
options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddVersionedApiExplorer(options =>
|
|
||||||
{
|
|
||||||
options.GroupNameFormat = "'v'VVV";
|
|
||||||
options.SubstituteApiVersionInUrl = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddEndpointsApiExplorer();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Settings;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class CacheConfiguration
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var cache = configuration.Get<CacheSettings>();
|
|
||||||
if (cache?.TypeDatabase == CacheSettings.CacheEnum.Redis)
|
|
||||||
{
|
|
||||||
services.AddStackExchangeRedisCache(options =>
|
|
||||||
{
|
|
||||||
options.Configuration = cache.ConnectionString;
|
|
||||||
options.InstanceName = "mirea_";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class EnvironmentConfiguration
|
|
||||||
{
|
|
||||||
private static IDictionary<string, string> LoadEnvironment(string envFile)
|
|
||||||
{
|
|
||||||
Dictionary<string, string> environment = new();
|
|
||||||
|
|
||||||
if (!File.Exists(envFile)) return environment;
|
|
||||||
|
|
||||||
foreach (var line in File.ReadAllLines(envFile))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(line)) continue;
|
|
||||||
|
|
||||||
var commentIndex = line.IndexOf('#', StringComparison.Ordinal);
|
|
||||||
|
|
||||||
string arg = line;
|
|
||||||
|
|
||||||
if (commentIndex != -1)
|
|
||||||
arg = arg.Remove(commentIndex, arg.Length - commentIndex);
|
|
||||||
|
|
||||||
var parts = arg.Split(
|
|
||||||
'=',
|
|
||||||
StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
if (parts.Length > 2)
|
|
||||||
parts = [parts[0], string.Join("=", parts[1..])];
|
|
||||||
|
|
||||||
if (parts.Length != 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
environment.Add(parts[0].Trim(), parts[1].Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
return environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IConfigurationRoot GetEnvironment()
|
|
||||||
{
|
|
||||||
var variablesFromFile = LoadEnvironment(".env");
|
|
||||||
|
|
||||||
var environmentVariables = Environment.GetEnvironmentVariables()
|
|
||||||
.OfType<DictionaryEntry>()
|
|
||||||
.ToDictionary(
|
|
||||||
entry => entry.Key.ToString() ?? string.Empty,
|
|
||||||
entry => entry.Value?.ToString() ?? string.Empty
|
|
||||||
);
|
|
||||||
|
|
||||||
var result = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(environmentVariables!)
|
|
||||||
.AddInMemoryCollection(variablesFromFile!);
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
result.AddInMemoryCollection(LoadEnvironment(".env.develop")!);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable("PATH_TO_SAVE", variablesFromFile["PATH_TO_SAVE"]);
|
|
||||||
|
|
||||||
return result.Build();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Mirea.Api.Endpoint.Common.Services.Security;
|
|
||||||
using Mirea.Api.Security.Common.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class JwtConfiguration
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddJwtToken(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!));
|
|
||||||
|
|
||||||
var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty);
|
|
||||||
|
|
||||||
if (jwtDecrypt.Length != 32)
|
|
||||||
throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length);
|
|
||||||
|
|
||||||
var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty);
|
|
||||||
|
|
||||||
if (jwtKey.Length != 64)
|
|
||||||
throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length);
|
|
||||||
|
|
||||||
var jwtIssuer = configuration["SECURITY_JWT_ISSUER"];
|
|
||||||
var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"];
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer))
|
|
||||||
throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified");
|
|
||||||
|
|
||||||
services.AddSingleton<IAccessToken, JwtTokenService>(_ => new JwtTokenService
|
|
||||||
{
|
|
||||||
Audience = jwtAudience,
|
|
||||||
Issuer = jwtIssuer,
|
|
||||||
Lifetime = lifeTimeJwt,
|
|
||||||
EncryptionKey = jwtDecrypt,
|
|
||||||
SigningKey = jwtKey
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddAuthentication(options =>
|
|
||||||
{
|
|
||||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
}).AddJwtBearer(options =>
|
|
||||||
{
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateIssuer = true,
|
|
||||||
ValidIssuer = jwtIssuer,
|
|
||||||
|
|
||||||
ValidateAudience = true,
|
|
||||||
ValidAudience = jwtAudience,
|
|
||||||
|
|
||||||
ValidateLifetime = true,
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
|
||||||
TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Mirea.Api.Endpoint.Common.Services;
|
|
||||||
using Mirea.Api.Endpoint.Configuration.General;
|
|
||||||
using Serilog;
|
|
||||||
using Serilog.Events;
|
|
||||||
using Serilog.Filters;
|
|
||||||
using Serilog.Formatting.Compact;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class LoggerConfiguration
|
|
||||||
{
|
|
||||||
public static IHostBuilder AddCustomSerilog(this IHostBuilder hostBuilder)
|
|
||||||
{
|
|
||||||
hostBuilder.UseSerilog((context, _, configuration) =>
|
|
||||||
{
|
|
||||||
var generalConfig = context.Configuration.Get<GeneralConfig>();
|
|
||||||
configuration
|
|
||||||
.MinimumLevel.Debug()
|
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
|
||||||
.Enrich.FromLogContext()
|
|
||||||
.WriteTo.Console(
|
|
||||||
outputTemplate:
|
|
||||||
"[{Level:u3}] [{Timestamp:dd.MM.yyyy HH:mm:ss}] {Message:lj}{NewLine}{Exception}");
|
|
||||||
|
|
||||||
if (generalConfig?.LogSettings?.EnableLogToFile == true)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(generalConfig.LogSettings.LogFilePath) && Directory.Exists(PathBuilder.Combine(generalConfig.LogSettings.LogFilePath)))
|
|
||||||
Directory.CreateDirectory(generalConfig.LogSettings.LogFilePath);
|
|
||||||
|
|
||||||
configuration.WriteTo.File(
|
|
||||||
new CompactJsonFormatter(),
|
|
||||||
PathBuilder.Combine(
|
|
||||||
generalConfig.LogSettings.LogFilePath!,
|
|
||||||
generalConfig.LogSettings.LogFileName + ".json"
|
|
||||||
),
|
|
||||||
LogEventLevel.Debug,
|
|
||||||
rollingInterval: RollingInterval.Day);
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration
|
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
|
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning);
|
|
||||||
|
|
||||||
configuration.Filter.ByExcluding(Matching.WithProperty<string>("SourceContext", sc =>
|
|
||||||
sc.Contains("Microsoft.EntityFrameworkCore.Database.Command")));
|
|
||||||
});
|
|
||||||
|
|
||||||
return hostBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IApplicationBuilder UseCustomSerilog(this IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
app.UseSerilogRequestLogging(options =>
|
|
||||||
{
|
|
||||||
options.MessageTemplate = "Handled {RequestPath} in {Elapsed:0.0000} ms";
|
|
||||||
|
|
||||||
options.GetLevel = (_, elapsed, ex) => elapsed >= 2500 || ex != null
|
|
||||||
? LogEventLevel.Warning
|
|
||||||
: elapsed >= 1000
|
|
||||||
? LogEventLevel.Information
|
|
||||||
: LogEventLevel.Debug;
|
|
||||||
|
|
||||||
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
|
|
||||||
{
|
|
||||||
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
|
|
||||||
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
|
|
||||||
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent);
|
|
||||||
diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString());
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Mirea.Api.Endpoint.Common.Services.Security;
|
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Settings;
|
|
||||||
using Mirea.Api.Security;
|
|
||||||
using Mirea.Api.Security.Common.Interfaces;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class SecureConfiguration
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddSecurity(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.AddSecurityServices(configuration);
|
|
||||||
|
|
||||||
services.AddSingleton<IAccessToken, JwtTokenService>();
|
|
||||||
services.AddSingleton<IRevokedToken, MemoryRevokedTokenService>();
|
|
||||||
|
|
||||||
if (configuration.Get<CacheSettings>()?.TypeDatabase == CacheSettings.CacheEnum.Redis)
|
|
||||||
services.AddSingleton<ICacheService, DistributedCacheService>();
|
|
||||||
else
|
|
||||||
services.AddSingleton<ICacheService, MemoryCacheService>();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Mirea.Api.Endpoint.Configuration.Swagger;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.AppConfig;
|
|
||||||
|
|
||||||
public static class SwaggerConfiguration
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddCustomSwagger(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSwaggerGen(options =>
|
|
||||||
{
|
|
||||||
options.SchemaFilter<SwaggerExampleFilter>();
|
|
||||||
options.OperationFilter<SwaggerDefaultValues>();
|
|
||||||
var basePath = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
|
|
||||||
options.IncludeXmlComments(Path.Combine(basePath, "docs.xml"));
|
|
||||||
options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml"));
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IApplicationBuilder UseCustomSwagger(this IApplicationBuilder app, IServiceProvider services)
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI(options =>
|
|
||||||
{
|
|
||||||
var provider = services.GetService<IApiVersionDescriptionProvider>();
|
|
||||||
|
|
||||||
foreach (var description in provider!.ApiVersionDescriptions)
|
|
||||||
{
|
|
||||||
var url = $"/swagger/{description.GroupName}/swagger.json";
|
|
||||||
var name = description.GroupName.ToUpperInvariant();
|
|
||||||
options.SwaggerEndpoint(url, name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
36
Endpoint/Configuration/EnvironmentManager.cs
Normal file
36
Endpoint/Configuration/EnvironmentManager.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Mirea.Api.Endpoint.Configuration;
|
||||||
|
|
||||||
|
internal static class EnvironmentManager
|
||||||
|
{
|
||||||
|
public static void LoadEnvironment(string envFile)
|
||||||
|
{
|
||||||
|
if (!File.Exists(envFile)) return;
|
||||||
|
|
||||||
|
foreach (var line in File.ReadAllLines(envFile))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(line)) continue;
|
||||||
|
|
||||||
|
var commentIndex = line.IndexOf('#', StringComparison.Ordinal);
|
||||||
|
|
||||||
|
string arg = line;
|
||||||
|
|
||||||
|
if (commentIndex != -1)
|
||||||
|
arg = arg.Remove(commentIndex, arg.Length - commentIndex);
|
||||||
|
|
||||||
|
var parts = arg.Split(
|
||||||
|
'=',
|
||||||
|
StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (parts.Length > 2)
|
||||||
|
parts = [parts[0], string.Join("=", parts[1..])];
|
||||||
|
|
||||||
|
if (parts.Length != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using Mirea.Api.DataAccess.Persistence.Common;
|
using System;
|
||||||
|
using Mirea.Api.DataAccess.Persistence.Common;
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Attributes;
|
using Mirea.Api.Endpoint.Configuration.General.Attributes;
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
|
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
|
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Mirea.Api.Endpoint.Common.Attributes;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint.Configuration.Swagger;
|
|
||||||
|
|
||||||
public class SwaggerExampleFilter : ISchemaFilter
|
|
||||||
{
|
|
||||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
|
||||||
{
|
|
||||||
var att = context.ParameterInfo?.GetCustomAttribute<SwaggerDefaultAttribute>();
|
|
||||||
if (att != null)
|
|
||||||
schema.Example = new Microsoft.OpenApi.Any.OpenApiString(att.Value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,17 +24,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Cronos" Version="0.8.4" />
|
<PackageReference Include="Cronos" Version="0.8.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
<PackageReference Include="StackExchange.Redis" Version="2.7.33" />
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.6" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,77 +1,173 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Versioning;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Mirea.Api.DataAccess.Application;
|
using Mirea.Api.DataAccess.Application;
|
||||||
using Mirea.Api.DataAccess.Persistence;
|
using Mirea.Api.DataAccess.Persistence;
|
||||||
using Mirea.Api.DataAccess.Persistence.Common;
|
using Mirea.Api.DataAccess.Persistence.Common;
|
||||||
using Mirea.Api.Endpoint.Common.Interfaces;
|
using Mirea.Api.Endpoint.Common.Interfaces;
|
||||||
using Mirea.Api.Endpoint.Common.Services;
|
using Mirea.Api.Endpoint.Common.Services;
|
||||||
using Mirea.Api.Endpoint.Configuration.AppConfig;
|
using Mirea.Api.Endpoint.Common.Services.Security;
|
||||||
|
using Mirea.Api.Endpoint.Configuration;
|
||||||
using Mirea.Api.Endpoint.Configuration.General;
|
using Mirea.Api.Endpoint.Configuration.General;
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Settings;
|
using Mirea.Api.Endpoint.Configuration.General.Settings;
|
||||||
using Mirea.Api.Endpoint.Configuration.General.Validators;
|
using Mirea.Api.Endpoint.Configuration.General.Validators;
|
||||||
|
using Mirea.Api.Endpoint.Configuration.Swagger;
|
||||||
using Mirea.Api.Endpoint.Middleware;
|
using Mirea.Api.Endpoint.Middleware;
|
||||||
|
using Mirea.Api.Security.Common.Interfaces;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Mirea.Api.Endpoint;
|
namespace Mirea.Api.Endpoint;
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddDatabase(IServiceCollection services, IConfiguration configuration)
|
private static IConfigurationRoot ConfigureEnvironment()
|
||||||
{
|
{
|
||||||
var dbSettings = configuration.Get<DbSettings>();
|
EnvironmentManager.LoadEnvironment(".env");
|
||||||
services.AddApplication();
|
var environmentVariables = Environment.GetEnvironmentVariables()
|
||||||
services.AddPersistence(
|
.OfType<DictionaryEntry>()
|
||||||
dbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite,
|
.ToDictionary(
|
||||||
dbSettings?.ConnectionStringSql ?? string.Empty);
|
entry => entry.Key.ToString() ?? string.Empty,
|
||||||
|
entry => entry.Value?.ToString() ?? string.Empty
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = new ConfigurationBuilder().AddInMemoryCollection(environmentVariables!);
|
||||||
|
|
||||||
|
return result.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceCollection ConfigureJwtToken(IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!));
|
||||||
|
|
||||||
|
var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty);
|
||||||
|
|
||||||
|
if (jwtDecrypt.Length != 32)
|
||||||
|
throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length);
|
||||||
|
|
||||||
|
var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty);
|
||||||
|
|
||||||
|
if (jwtKey.Length != 64)
|
||||||
|
throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length);
|
||||||
|
|
||||||
|
var jwtIssuer = configuration["SECURITY_JWT_ISSUER"];
|
||||||
|
var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"];
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer))
|
||||||
|
throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified");
|
||||||
|
|
||||||
|
services.AddSingleton<IAccessToken, JwtTokenService>(_ => new JwtTokenService
|
||||||
|
{
|
||||||
|
Audience = jwtAudience,
|
||||||
|
Issuer = jwtIssuer,
|
||||||
|
Lifetime = lifeTimeJwt,
|
||||||
|
EncryptionKey = jwtDecrypt,
|
||||||
|
SigningKey = jwtKey
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
}).AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidIssuer = jwtIssuer,
|
||||||
|
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidAudience = jwtAudience,
|
||||||
|
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
||||||
|
TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IServiceCollection ConfigureSecurity(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IAccessToken, JwtTokenService>();
|
||||||
|
services.AddSingleton<IRevokedToken, MemoryRevokedTokenService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
|
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment());
|
builder.Configuration.AddConfiguration(ConfigureEnvironment());
|
||||||
builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true);
|
builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true);
|
||||||
builder.Services.Configure<GeneralConfig>(builder.Configuration);
|
builder.Services.Configure<GeneralConfig>(builder.Configuration);
|
||||||
|
|
||||||
builder.Host.AddCustomSerilog();
|
var generalConfig = builder.Configuration.Get<GeneralConfig>();
|
||||||
AddDatabase(builder.Services, builder.Configuration);
|
builder.Services.AddApplication();
|
||||||
|
builder.Services.AddPersistence(generalConfig?.DbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, generalConfig?.DbSettings?.ConnectionStringSql ?? string.Empty);
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IMaintenanceModeNotConfigureService, MaintenanceModeNotConfigureService>();
|
builder.Services.AddSingleton<IMaintenanceModeNotConfigureService, MaintenanceModeNotConfigureService>();
|
||||||
builder.Services.AddSingleton<IMaintenanceModeService, MaintenanceModeService>();
|
builder.Services.AddSingleton<IMaintenanceModeService, MaintenanceModeService>();
|
||||||
builder.Services.AddSingleton<ISetupToken, SetupTokenService>();
|
builder.Services.AddSingleton<ISetupToken, SetupTokenService>();
|
||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
|
||||||
builder.Services.AddCustomRedis(builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("AllowAll", policy =>
|
options.AddPolicy("AllowAll", policy =>
|
||||||
{
|
{
|
||||||
policy.AllowAnyHeader();
|
policy.AllowAnyHeader();
|
||||||
policy.AllowAnyMethod();
|
policy.AllowAnyMethod();
|
||||||
policy.WithOrigins("http://localhost:4200");
|
policy.AllowAnyOrigin();
|
||||||
policy.AllowCredentials();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddCustomApiVersioning();
|
builder.Services.AddApiVersioning(options =>
|
||||||
builder.Services.AddCustomSwagger();
|
{
|
||||||
|
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||||
|
options.AssumeDefaultVersionWhenUnspecified = true;
|
||||||
|
options.ReportApiVersions = true;
|
||||||
|
options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddJwtToken(builder.Configuration);
|
builder.Services.AddVersionedApiExplorer(options =>
|
||||||
builder.Services.AddSecurity(builder.Configuration);
|
{
|
||||||
|
options.GroupNameFormat = "'v'VVV";
|
||||||
|
options.SubstituteApiVersionInUrl = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
|
builder.Services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.OperationFilter<SwaggerDefaultValues>();
|
||||||
|
var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);
|
||||||
|
|
||||||
|
var xmlPath = Path.Combine(basePath, "docs.xml");
|
||||||
|
options.IncludeXmlComments(xmlPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseCors("AllowAll");
|
#if DEBUG
|
||||||
app.UseCustomSerilog();
|
// Write configurations
|
||||||
|
foreach (var item in app.Configuration.AsEnumerable())
|
||||||
|
Console.WriteLine($"{item.Key}:{item.Value}");
|
||||||
|
#endif
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
@ -88,11 +184,27 @@ public class Program
|
|||||||
|
|
||||||
maintenanceModeService.DisableMaintenanceMode();
|
maintenanceModeService.DisableMaintenanceMode();
|
||||||
DbInitializer.Initialize(uberDbContext);
|
DbInitializer.Initialize(uberDbContext);
|
||||||
|
|
||||||
|
// todo: if admin not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseCustomSwagger(app.Services);
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(options =>
|
||||||
|
{
|
||||||
|
var provider = app.Services.GetService<IApiVersionDescriptionProvider>();
|
||||||
|
|
||||||
|
foreach (var description in provider!.ApiVersionDescriptions)
|
||||||
|
{
|
||||||
|
var url = $"/swagger/{description.GroupName}/swagger.json";
|
||||||
|
var name = description.GroupName.ToUpperInvariant();
|
||||||
|
options.SwaggerEndpoint(url, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
app.UseMiddleware<MaintenanceModeMiddleware>();
|
app.UseMiddleware<MaintenanceModeMiddleware>();
|
||||||
app.UseMiddleware<CustomExceptionHandlerMiddleware>();
|
app.UseMiddleware<CustomExceptionHandlerMiddleware>();
|
||||||
|
|
||||||
@ -100,6 +212,7 @@ public class Program
|
|||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="FluentValidation" Version="11.9.1" />
|
<PackageReference Include="FluentValidation" Version="11.9.1" />
|
||||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user