Rename WebApi project to Vegasco.Server.Api
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
And update all references including comments etc.
This commit is contained in:
9
src/Vegasco.Server.Api/Common/Constants.cs
Normal file
9
src/Vegasco.Server.Api/Common/Constants.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static class Authorization
|
||||
{
|
||||
public const string RequireAuthenticatedUserPolicy = "RequireAuthenticatedUser";
|
||||
}
|
||||
}
|
||||
132
src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs
Normal file
132
src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Asp.Versioning;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Vegasco.Server.Api.Authentication;
|
||||
using Vegasco.Server.Api.Common;
|
||||
using Vegasco.Server.Api.Persistence;
|
||||
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds all the Api related services to the Dependency Injection container.
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
public static void AddApiServices(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.Services
|
||||
.AddMiscellaneousServices()
|
||||
.AddCustomOpenApi()
|
||||
.AddApiVersioning()
|
||||
.AddAuthenticationAndAuthorization(builder.Environment);
|
||||
|
||||
builder.AddDbContext();
|
||||
}
|
||||
|
||||
private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddResponseCompression();
|
||||
|
||||
services.AddValidatorsFromAssemblies(
|
||||
[
|
||||
typeof(IApiMarker).Assembly
|
||||
], ServiceLifetime.Singleton);
|
||||
|
||||
services.AddHealthChecks();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddHostedService<ApplyMigrationsService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddCustomOpenApi(this IServiceCollection services)
|
||||
{
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddOpenApi(o =>
|
||||
{
|
||||
o.CreateSchemaReferenceId = jsonTypeInfo =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonTypeInfo.Type.FullName))
|
||||
{
|
||||
return jsonTypeInfo.Type.Name;
|
||||
}
|
||||
|
||||
string? fullClassName = jsonTypeInfo.Type.FullName;
|
||||
|
||||
if (!string.IsNullOrEmpty(jsonTypeInfo.Type.Namespace))
|
||||
{
|
||||
fullClassName = fullClassName
|
||||
.Replace(jsonTypeInfo.Type.Namespace, "")
|
||||
.TrimStart('.');
|
||||
}
|
||||
|
||||
fullClassName = fullClassName.Replace('+', '_');
|
||||
return fullClassName;
|
||||
};
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddApiVersioning(this IServiceCollection services)
|
||||
{
|
||||
services.AddApiVersioning(o =>
|
||||
{
|
||||
o.DefaultApiVersion = new ApiVersion(1);
|
||||
o.ApiVersionReader = new UrlSegmentApiVersionReader();
|
||||
o.ReportApiVersions = true;
|
||||
})
|
||||
.AddApiExplorer(o =>
|
||||
{
|
||||
o.GroupNameFormat = "'v'V";
|
||||
o.SubstituteApiVersionInUrl = true;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddAuthenticationAndAuthorization(this IServiceCollection services, IHostEnvironment environment)
|
||||
{
|
||||
services.AddOptions<JwtOptions>()
|
||||
.BindConfiguration(JwtOptions.SectionName)
|
||||
.ValidateFluently()
|
||||
.ValidateOnStart();
|
||||
|
||||
var jwtOptions = services.BuildServiceProvider().GetRequiredService<IOptions<JwtOptions>>();
|
||||
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
|
||||
{
|
||||
o.MetadataAddress = jwtOptions.Value.MetadataUrl;
|
||||
|
||||
o.TokenValidationParameters.ValidAudience = jwtOptions.Value.ValidAudience;
|
||||
o.TokenValidationParameters.ValidateAudience = true;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(jwtOptions.Value.NameClaimType))
|
||||
{
|
||||
o.TokenValidationParameters.NameClaimType = jwtOptions.Value.NameClaimType;
|
||||
}
|
||||
|
||||
o.RequireHttpsMetadata = !jwtOptions.Value.AllowHttpMetadataUrl && !environment.IsDevelopment();
|
||||
});
|
||||
|
||||
services.AddAuthorizationBuilder()
|
||||
.AddPolicy(Constants.Authorization.RequireAuthenticatedUserPolicy, p => p
|
||||
.RequireAuthenticatedUser()
|
||||
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme));
|
||||
|
||||
services.AddScoped<UserAccessor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IHostApplicationBuilder AddDbContext(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.AddNpgsqlDbContext<ApplicationDbContext>(AppHost.Shared.Constants.Database.Name);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
37
src/Vegasco.Server.Api/Common/FluentValidationOptions.cs
Normal file
37
src/Vegasco.Server.Api/Common/FluentValidationOptions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
public class FluentValidationOptions<TOptions> : IValidateOptions<TOptions>
|
||||
where TOptions : class
|
||||
{
|
||||
private readonly IEnumerable<IValidator<TOptions>> _validators;
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public FluentValidationOptions(string? name, IEnumerable<IValidator<TOptions>> validators)
|
||||
{
|
||||
Name = name;
|
||||
_validators = validators;
|
||||
}
|
||||
|
||||
public ValidateOptionsResult Validate(string? name, TOptions options)
|
||||
{
|
||||
if (name is not null && name != Name)
|
||||
{
|
||||
return ValidateOptionsResult.Skip;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
List<ValidationResult> failedValidations = _validators.ValidateAllAsync(options).Result;
|
||||
if (failedValidations.Count == 0)
|
||||
{
|
||||
return ValidateOptionsResult.Success;
|
||||
}
|
||||
|
||||
return ValidateOptionsResult.Fail(failedValidations.SelectMany(x => x.Errors.Select(x => x.ErrorMessage)));
|
||||
}
|
||||
}
|
||||
3
src/Vegasco.Server.Api/Common/IApiMarker.cs
Normal file
3
src/Vegasco.Server.Api/Common/IApiMarker.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
public interface IApiMarker;
|
||||
53
src/Vegasco.Server.Api/Common/StartupExtensions.cs
Normal file
53
src/Vegasco.Server.Api/Common/StartupExtensions.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using System.Globalization;
|
||||
using Vegasco.Server.Api.Endpoints;
|
||||
using Vegasco.Server.ServiceDefaults;
|
||||
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
internal static class StartupExtensions
|
||||
{
|
||||
internal static WebApplication ConfigureServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Configuration.AddEnvironmentVariables("Vegasco_");
|
||||
|
||||
builder.AddApiServices();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
return app;
|
||||
}
|
||||
|
||||
internal static WebApplication ConfigureRequestPipeline(this WebApplication app)
|
||||
{
|
||||
app.UseRequestLocalization(o =>
|
||||
{
|
||||
o.SupportedCultures =
|
||||
[
|
||||
new CultureInfo("en")
|
||||
];
|
||||
|
||||
o.SupportedUICultures = o.SupportedCultures;
|
||||
|
||||
CultureInfo defaultCulture = o.SupportedCultures[0];
|
||||
o.DefaultRequestCulture = new RequestCulture(defaultCulture);
|
||||
});
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapEndpoints();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi("/swagger/{documentName}/swagger.json");
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
62
src/Vegasco.Server.Api/Common/ValidatorExtensions.cs
Normal file
62
src/Vegasco.Server.Api/Common/ValidatorExtensions.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Vegasco.Server.Api.Common;
|
||||
|
||||
public static class ValidatorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously validates an instance of <typeparamref name="T"/> against all <see cref="IValidator{T}"/> instances in <paramref name="validators"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="validators"></param>
|
||||
/// <param name="instance"></param>
|
||||
/// <returns>The failed validation results.</returns>
|
||||
public static async Task<List<ValidationResult>> ValidateAllAsync<T>(this IEnumerable<IValidator<T>> validators, T instance, CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<Task<ValidationResult>> validationTasks = validators
|
||||
.Select(validator => validator.ValidateAsync(instance, cancellationToken))
|
||||
.ToList();
|
||||
|
||||
await Task.WhenAll(validationTasks);
|
||||
|
||||
List<ValidationResult> failedValidations = validationTasks
|
||||
.Select(x => x.Result)
|
||||
.Where(x => !x.IsValid)
|
||||
.ToList();
|
||||
|
||||
return failedValidations;
|
||||
}
|
||||
|
||||
public static Dictionary<string, string[]> ToCombinedDictionary(this IEnumerable<ValidationResult> validationResults)
|
||||
{
|
||||
// Use a hash set to avoid duplicate error messages.
|
||||
Dictionary<string, HashSet<string>> combinedErrors = [];
|
||||
|
||||
foreach (ValidationFailure? error in validationResults.SelectMany(x => x.Errors))
|
||||
{
|
||||
if (!combinedErrors.TryGetValue(error.PropertyName, out HashSet<string>? value))
|
||||
{
|
||||
value = [error.ErrorMessage];
|
||||
combinedErrors[error.PropertyName] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
value.Add(error.ErrorMessage);
|
||||
}
|
||||
|
||||
return combinedErrors.ToDictionary(x => x.Key, x => x.Value.ToArray());
|
||||
}
|
||||
|
||||
public static OptionsBuilder<T> ValidateFluently<T>(this OptionsBuilder<T> builder)
|
||||
where T : class
|
||||
{
|
||||
builder.Services.AddTransient<IValidateOptions<T>>(serviceProvider =>
|
||||
{
|
||||
IEnumerable<IValidator<T>> validators = serviceProvider.GetServices<IValidator<T>>() ?? [];
|
||||
return new FluentValidationOptions<T>(builder.Name, validators);
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user