Feature flags changed (#240)

This commit is contained in:
Ivan Paulovich 2020-09-24 22:26:41 +02:00 committed by GitHub
parent 53a8b1ca14
commit 0b9717a541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 372 additions and 172 deletions

View File

@ -1,22 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDsDCCApigAwIBAgIJAK4m+TZm7STwMA0GCSqGSIb3DQEBCwUAMDsxFTATBgNV
MIIDsDCCApigAwIBAgIJAKs4XZnqRLRoMA0GCSqGSIb3DQEBCwUAMDsxFTATBgNV
BAMTDHdhbGxldC5sb2NhbDEVMBMGA1UEChMMd2FsbGV0LmxvY2FsMQswCQYDVQQG
EwJVUzAeFw0yMDA5MjEyMTE0NDFaFw0yMTA5MjEyMTE0NDFaMDsxFTATBgNVBAMT
EwJVUzAeFw0yMDA5MjQyMDE2MDNaFw0yMTA5MjQyMDE2MDNaMDsxFTATBgNVBAMT
DHdhbGxldC5sb2NhbDEVMBMGA1UEChMMd2FsbGV0LmxvY2FsMQswCQYDVQQGEwJV
UzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALk5RtmYgIl/RQxKaPTi
BeU+7ugIj9nIIbp8ZMij/2NFkKvGPJDLxY+xyyVMkPWJjLXYBxxul/2ay15xqXEz
x1VqYg9bW9pAahdrCs37+BODJ56iilOUTWMlzgM3ZwaCH+lrL/QbexyiAOMmUWdf
MGQEAQTFEHhr+t7n9KJIiaDtiZavl4M3PcKN2SE+7BEcYpBLtTf1UXzYwqBYnimU
RGvyoQmh7rUkAMnfXzDo7FFdIVK6YUncRVo+AMzsJqHbJWnpdiJ10muDroKxyWX5
pV5cjECnBPb93OzocvqY03pvvfIJPZKJOluiqcUaq9byzq3Y3wmccjLYCnVcxegg
oAECAwEAAaOBtjCBszAXBgNVHREEEDAOggx3YWxsZXQubG9jYWwwHQYDVR0OBBYE
FE7r6XmbwiDVDF26vv2FaMEiZATzMGsGA1UdIwRkMGKAFE7r6XmbwiDVDF26vv2F
aMEiZATzoT+kPTA7MRUwEwYDVQQDEwx3YWxsZXQubG9jYWwxFTATBgNVBAoTDHdh
bGxldC5sb2NhbDELMAkGA1UEBhMCVVOCCQCuJvk2Zu0k8DAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBCwUAA4IBAQCsP9so5myEJ0kzSuVMx8w/hljZh53KbegE0hjT
1NA6oC9GPnYm1DCWPMDru/zGSIvEAJfI5XLNR+zxMCjjXWx4oH8eIzQG0YABAL1N
ewPUXU9wufYCvYGAAwoE/KcKBVgDnsjqhELOFTemg9AVBwfTwCT+IdZgR0KviktT
RSPmanIBBKd6lyrsgVcVU7y3Oab8ctOYmXCaEAnvaIc91d9L1qSymozw/3FgCLVl
xTZhe3uAAGXv1xVr+WF8VS7xbYs1/elLhHECHxZFIKpC+/CncP22bFzMfaP1zKRW
FNDO2x6m8/Wc+n9p3j/gPZC8QoYiUCHq43jT6a8j8SUcqxqV
UzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJb5UiiWih6VDBpHqbb
b3JSZ8fc5JvcY9dTgG6foeS+NR3Z3v0yKmkwvs+aZM0PAzx150hwYjTP/Ftt3C1z
SwTJCI6vMQwPiyUYwgBbBhQ0jp8/RolxZ+5Gztzzrowwk+r6woaHoq8iG2zXGWHD
n8ziz2jEAlZpVF+4LmclpzjU3hhP/APijeywynEvjbU0H5LD9/QO+FOC7C9nq8AW
MGDSE3RsYD/K8O7pqteIc85t4W52/7iES/T2qW8WMnRlVtrVsNyM6iIYUQUFWM6m
32voIEOedMnxQr1NCqJ7WUZVE8uVRg8yl0jHynWiZioTxtw7b139tx1+ABz+9GYB
AjMCAwEAAaOBtjCBszAXBgNVHREEEDAOggx3YWxsZXQubG9jYWwwHQYDVR0OBBYE
FA1WTby9hAL7KnYSo5AtZbAzParVMGsGA1UdIwRkMGKAFA1WTby9hAL7KnYSo5At
ZbAzParVoT+kPTA7MRUwEwYDVQQDEwx3YWxsZXQubG9jYWwxFTATBgNVBAoTDHdh
bGxldC5sb2NhbDELMAkGA1UEBhMCVVOCCQCrOF2Z6kS0aDAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBCwUAA4IBAQAJtA5CD1Lg8FupJ0fkDHGX7n+558krSz93DD8I
SRfq4jAhzRlfKyrg16UlL0HzPeDJgIMIQk2KYAh1a/0Q+YPvjpRqhfEWNvbqPobZ
/MXs3lkNKK1yDxfUf11iXiQggylwLmGFPC3psiA0KIPI0b+kvdN9GqjQnvIAnTlo
x87qnsrzKXw3V/jQjH57+YHbaBNI0JyB9Qy34bmaEqO/BaKEvfoQV0cDhItrDynj
9ErVvcP7FWaIct6SsDpYlFvFG2xypjZ3xoeh2+wB63teoSeGi13FMhzHjxF8Wydq
C/kF0Dfo4fHX1dghwHUCKWRKqrgArC3eYt2Itrgl3tiqByo4
-----END CERTIFICATE-----

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5OUbZmICJf0UM
Smj04gXlPu7oCI/ZyCG6fGTIo/9jRZCrxjyQy8WPscslTJD1iYy12Accbpf9mste
calxM8dVamIPW1vaQGoXawrN+/gTgyeeoopTlE1jJc4DN2cGgh/pay/0G3scogDj
JlFnXzBkBAEExRB4a/re5/SiSImg7YmWr5eDNz3CjdkhPuwRHGKQS7U39VF82MKg
WJ4plERr8qEJoe61JADJ318w6OxRXSFSumFJ3EVaPgDM7Cah2yVp6XYiddJrg66C
scll+aVeXIxApwT2/dzs6HL6mNN6b73yCT2SiTpboqnFGqvW8s6t2N8JnHIy2Ap1
XMXoIKABAgMBAAECggEAVkYliCkOhfzv7n+iT54P3O3pXc7PIkOj4jcawpIQ3O6E
Kl6Vy2P+y6Wm1+LICnZdQj4zYbzpssitPXp30DCsAk8hCcImEIy8XdgQGk3pCT2n
8K4I/FExkqMDSGA/7rohnxm8Jlm+iGG1RBiPBjVCVvO+fbhEqbKyEa29NvHOmVP/
Yi42aajfwU+iM/CpDWxtYdcCKdlrC2TbI5UpZHZj2hG2M/+hC2LluSxYanAXfnL8
gLHy5xAsc6+cQ8e4tM3WreC+cKYISitlxUEto+/PZZnqCmu3OyLepY6HasNEkC4h
dYVqprzKfktSiNROr6oZDcC2oHnh/GQZ15iCpAHc+QKBgQDvet+Rng/83SFBY9wp
g5TVXAaVOgKY7UwrxJ3H14kOvDRxILItCS8ZIbpuhi6tWOXAcKBL0avr6yl/f7cS
zOrWSfXmS0zLzs4//ZMYLjOqH+XfYL/JS/zkaUR5dhyKesIa07uxUowE5cGLsjSE
/w44eU4zCmsLmw3WyBBwJN8+fwKBgQDGAEI0sRSXLD5BEAiqhFv4OlJXfDuBqEGL
VPWmhZItFAvxTN541ix63oU8J1xAwr1cyxpWvWrFuApTFNn/jKCkxJzcmNAFyeFC
TU89y9z3KAteBhz9/O/hnbZIPLoDkJq4gZ9yazvG64YaO0wQ56+lfH9/hdJ/YWmS
sjnzRQ3hfwKBgDyHzTysPBRI/F/f0sU3egYX1Z4gZIms6STi9JKPmxf7YDH79q1F
df8fNFU9W0z7fE8Q6KNhYtglv353lJmO7Aiv9xR3kfit1Bn+iRCClRJ6L4svlCpY
J5rANMuSnBIwhyFqxTVDzRprzGj36Sh4o2qI1sg16OqtDWltdiIyX68XAoGAYc48
5fRXHHnBYrg936U0HQEKLabzag+oGOBl29T9KQFPCOoUtxHstOD4ZYd33ZGUoqK9
Jv24IWTQzMtvB/n6XIm2ho2BrGpVuD+iFf9+7iO8wbw5VAk+oW8bXMGKjTpAA0Yc
chOgWpl7C7gg0fTwYcmG3YJIQZ3n3YvmblG9EpcCgYEAjAEe9ip1QEC4UehtEp4i
mSSlE+yl2Xcc0SYoPzORExsK8jAXigkYMV/8ZG1eXZ1cZ+h0pg8UGi3MpOoHbPie
9xmMaUy+isz8rGPrFEtWVbj5QnJfXhEvURzm0rGwumzaPGJPMmX+OcYGG+WXYO5B
g6kNacMykAygKVCf8aG8La4=
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyW+VIolooelQw
aR6m229yUmfH3OSb3GPXU4Bun6HkvjUd2d79MippML7PmmTNDwM8dedIcGI0z/xb
bdwtc0sEyQiOrzEMD4slGMIAWwYUNI6fP0aJcWfuRs7c866MMJPq+sKGh6KvIhts
1xlhw5/M4s9oxAJWaVRfuC5nJac41N4YT/wD4o3ssMpxL421NB+Sw/f0DvhTguwv
Z6vAFjBg0hN0bGA/yvDu6arXiHPObeFudv+4hEv09qlvFjJ0ZVba1bDcjOoiGFEF
BVjOpt9r6CBDnnTJ8UK9TQqie1lGVRPLlUYPMpdIx8p1omYqE8bcO29d/bcdfgAc
/vRmAQIzAgMBAAECggEBAJAKdQYOz58HA/zAuFdquftqn5OiSiGxgBRCxGMSmk1A
a2c7L8HthKDFFCRW/Pc6G5P1aB5xXG5oVdaEx58REFHCBR4uT/78gpQoBW0CFcu1
kkT7dfzD0Hp3RtLcHWfsR1kqwHSjD3g+pEVnBYGV2fU+auCiA31DvTc8aIU2vHDA
5oJBYlm4UBq3kdvkb6z8vDKH8sl3tuO03APSzzILvFz6Ucubcr/vWFDjiNYiMnNU
4F3yO+nEJ5OJeHzik5xFH91B18qYICe21a506+lt8nsezx7poeQMkhLbuBWo1TJt
vLIVQF/rrb1qlztDf9bGCbR4Q/S2SV4SA6PRJUPL25kCgYEA57jV+LCZRfOlxIYv
5ShRJhSZHG94wlFRWT4qq7ZwYIStmr51FsUyzVFffTHnNtRHr61Sq/6cjnmIqSPZ
6IVf5ZiYcXN5Kxha1CPLVrccya8myPeXGdHnvZd0QOUWt8dTs61aO9svaJ0D0cbw
iQu3DIDW9pjgFMPZ57E+xDT9Jv8CgYEAxQvGgnsV4rfR2AsEZo3mfrLL5h5HtQgr
GofM9ZENA4xDbu0cFsIAETv2PCFQErDa1TT+/zY+7rLXJjpDPyN5LYz4UoTMKrn5
yXNYTe6cUzpWe+/+jGN3XAwLEUA2ARmcXLCLrXhhjQhZ1VDo1/68EdQ406jLeZzL
QsI4XAI/OM0CgYAThyUpHyDbn0viAvj4U+x6zBOXLOG9ZoL3hWCtNePEjone15wg
fh/o46mrcFAu87bS3EVyTzO8YgudTXMowEyzmaeHMBcPBa9p31ZciXP4nzUijJME
Twc7mA7pBSVj+X1ZUAuhrbgSFeFjhWNx+TXE7Pqwr3N67IJpnytN97U52wKBgCmt
kfFrY3eu9d3Chs3CWeyWmPY1lWk/Lw7PhpSyd5Q2iNTfpj9SDX2Xtz+EtFEfw0mx
ifYlXS5T+ZoGXM7wC+OMm8GogR7Qc3sewKDLRZl9D2nrm2XKbCa2DWk6sbGN3zrj
xMeOXOPdzlKGaJm3McR7pQFwBInWlk+qkQ01FHZJAoGAbTIgu57OxvGlfZu9fUc4
F18yOTGwOV2jIKsd9bnxNH6klXFDGEGa+fAO7B38lSaWnKacENS+F6LaSMxH55b+
8nWJI7v8lao/T7mZtmHoEZOYdjM/tCU+it9wP8DoIG5xHMx3k2v18AZYP3I2jN2B
EJR5+iT2sKFuM4SoMCkOmoE=
-----END PRIVATE KEY-----

9
.vscode/launch.json vendored
View File

@ -4,6 +4,15 @@
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": "Docker .NET Core Attach (Accounts APi)",
"type": "docker",
"request": "attach",
"platform": "netCore",
"sourceFileMap": {
"/src": "${workspaceFolder}/accounts-api"
}
},
{
"name": "Docker .NET Core Attach (Preview)",
"type": "docker",

View File

@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [3.5.0] - 2020-09-23
### Changed
- Feature Management changed.
## [3.4.0] - 2020-09-22
### Changed

View File

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>
3.4.0
3.5.0
</Version>
</PropertyGroup>
</Project>

View File

@ -50,7 +50,9 @@ namespace Infrastructure.DataAccess.Repositories
if (account != null)
{
this._context.Accounts.Remove(account);
this._context
.Accounts
.Remove(account);
}
}
@ -109,6 +111,12 @@ namespace Infrastructure.DataAccess.Repositories
.ToListAsync()
.ConfigureAwait(false);
foreach(Account findAccount in accounts)
{
await this.LoadTransactions(findAccount.AccountId, findAccount)
.ConfigureAwait(false);
}
return accounts;
}

View File

@ -6,6 +6,8 @@ namespace WebApi.Modules.Common
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Authentication Extensions.
@ -19,20 +21,17 @@ namespace WebApi.Modules.Common
this IServiceCollection services,
IConfiguration configuration)
{
bool useFake = configuration.GetValue<bool>("AuthenticationModule:UseFake");
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
if (useFake)
{
services.AddScoped<IUserService, TestUserService>();
bool isEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.Authentication))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = "Test";
x.DefaultChallengeScheme = "Test";
}).AddScheme<AuthenticationSchemeOptions, TestAuthenticationHandler>(
"Test", options => { });
}
else
if (isEnabled)
{
services.AddScoped<IUserService, ExternalUserService>();
@ -49,7 +48,17 @@ namespace WebApi.Modules.Common
// set the name of the API that's talking to the Identity API
});
}
else
{
services.AddScoped<IUserService, TestUserService>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = "Test";
x.DefaultChallengeScheme = "Test";
}).AddScheme<AuthenticationSchemeOptions, TestAuthenticationHandler>(
"Test", options => { });
}
return services;
}

View File

@ -1,7 +1,12 @@
namespace WebApi.Modules.Common
{
using Microsoft.Extensions.DependencyInjection;
{
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using System.Text.Json;
using System.Text.Json.Serialization;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Custom Controller Extensions.
/// </summary>
@ -12,12 +17,36 @@ namespace WebApi.Modules.Common
/// </summary>
public static IServiceCollection AddCustomControllers(this IServiceCollection services)
{
services
.AddMvc(options => { options.Filters.Add(typeof(ExceptionFilter)); });
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
bool isErrorFilterEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.ErrorFilter))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
services
.AddHttpContextAccessor()
.AddControllers()
.AddMvc(options =>
{
options.OutputFormatters.RemoveType<TextOutputFormatter>();
options.OutputFormatters.RemoveType<StreamOutputFormatter>();
options.RespectBrowserAcceptHeader = true;
if (isErrorFilterEnabled)
{
options.Filters.Add(new ExceptionFilter());
}
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
})
.AddControllersAsServices();
return services;

View File

@ -15,12 +15,11 @@ namespace WebApi.Modules.Common
{
var problemDetails = new ProblemDetails
{
Status = 400,
Title = "Bad Request",
Detail = context.Exception.Message
Status = 500,
Title = "Bad Request"
};
context.Result = new BadRequestObjectResult(problemDetails);
context.Result = new JsonResult(problemDetails);
context.Exception = null;
}
}

View File

@ -5,14 +5,57 @@ namespace WebApi.Modules.Common.FeatureFlags
/// </summary>
public enum CustomFeature
{
/// <summary>
/// Close Account.
/// </summary>
CloseAccount,
/// <summary>
/// Deposit.
/// </summary>
Deposit,
/// <summary>
/// Get Account.
/// </summary>
GetAccount,
/// <summary>
/// Get Accounts.
/// </summary>
GetAccounts,
/// <summary>
/// Open Account.
/// </summary>
OpenAccount,
/// <summary>
/// Transfer Feature.
/// </summary>
Transfer,
/// <summary>
/// Get Account Details V2 Feature.
/// Withdraw.
/// </summary>
GetAccountDetailsV2
Withdraw,
/// <summary>
/// Get Account V2.
/// </summary>
GetAccountV2,
/// <summary>
/// Filter errors out.
/// </summary>
ErrorFilter,
/// <summary>
/// Use Swagger.
/// </summary>
Swagger,
/// <summary>
/// Use SQL Server Persistence.
/// </summary>
SQLServer,
/// <summary>
/// Uses external exchange service.
/// </summary>
CurrencyExchange,
/// <summary>
/// Use authentication.
/// </summary>
Authentication
}
}

View File

@ -12,15 +12,13 @@ namespace WebApi.Modules.Common
/// Add Prometheus dependencies.
/// </summary>
public static IApplicationBuilder UseCustomHttpMetrics(this IApplicationBuilder appBuilder) =>
appBuilder.UseMetricServer()
appBuilder
.UseMetricServer()
.UseHttpMetrics(options =>
{
options.RequestDuration.Enabled = false;
options.InProgress.Enabled = false;
options.RequestCount.Counter = Metrics.CreateCounter(
"http_requests_total",
"HTTP Requests Total",
new CounterConfiguration { LabelNames = new[] { "controller", "method", "code" } });
options.RequestDuration.Enabled = true;
options.InProgress.Enabled = true;
options.RequestCount.Enabled = true;
});
}
}

View File

@ -7,13 +7,15 @@ namespace WebApi.Modules.Common.Swagger
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.FeatureManagement;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Reflection;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Swagger Extensions.
/// </summary>
@ -33,35 +35,48 @@ namespace WebApi.Modules.Common.Swagger
/// <summary>
/// Add Swagger Configuration dependencies.
/// </summary>
[SuppressMessage("Minor Code Smell", "S1075:URIs should not be hardcoded", Justification = "<Pending>")]
public static IServiceCollection AddSwagger(this IServiceCollection services)
{
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
_ = services.AddSwaggerGen(
c =>
{
c.IncludeXmlComments(XmlCommentsFilePath);
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
bool isEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.Swagger))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
if (isEnabled)
{
services
.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>()
.AddSwaggerGen(
c =>
{
c.IncludeXmlComments(XmlCommentsFilePath);
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
}
return services;
}

View File

@ -4,6 +4,8 @@ namespace WebApi.Modules
using Infrastructure.CurrencyExchange;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Currency Exchange Extensions.
@ -17,16 +19,25 @@ namespace WebApi.Modules
this IServiceCollection services,
IConfiguration configuration)
{
bool useFake = configuration.GetValue<bool>("CurrencyExchangeModule:UseFake");
if (useFake)
{
services.AddScoped<ICurrencyExchange, CurrencyExchangeFake>();
}
else
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
bool isEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.CurrencyExchange))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
if (isEnabled)
{
services.AddHttpClient(CurrencyExchangeService.HttpClientName);
services.AddScoped<ICurrencyExchange, CurrencyExchangeService>();
}
else
{
services.AddScoped<ICurrencyExchange, CurrencyExchangeFake>();
}
return services;
}

View File

@ -6,13 +6,15 @@ namespace WebApi.Modules
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.FeatureManagement;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using System.Threading.Tasks;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// HealthChecks Extensions.
/// </summary>
@ -27,12 +29,21 @@ namespace WebApi.Modules
{
IHealthChecksBuilder healthChecks = services.AddHealthChecks();
bool useFake = configuration.GetValue<bool>("PersistenceModule:UseFake");
if (!useFake)
{
healthChecks.AddDbContextCheck<MangaContext>("MangaDbContext");
}
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
bool isEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.SQLServer))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
if (isEnabled)
{
healthChecks.AddDbContextCheck<MangaContext>("MangaDbContext");
}
return services;
}

View File

@ -7,27 +7,34 @@ namespace WebApi.Modules
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Persistence Extensions.
/// </summary>
public static class PersistenceExtensions
#pragma warning disable S101 // Types should be named in PascalCase
public static class SQLServerExtensions
#pragma warning restore S101 // Types should be named in PascalCase
{
/// <summary>
/// Add Persistence dependencies varying on configuration.
/// </summary>
public static IServiceCollection AddPersistence(
public static IServiceCollection AddSQLServer(
this IServiceCollection services,
IConfiguration configuration)
{
bool useFake = configuration.GetValue<bool>("PersistenceModule:UseFake");
if (useFake)
{
services.AddSingleton<MangaContextFake, MangaContextFake>();
services.AddScoped<IUnitOfWork, UnitOfWorkFake>();
services.AddScoped<IAccountRepository, AccountRepositoryFake>();
}
else
IFeatureManager featureManager = services
.BuildServiceProvider()
.GetRequiredService<IFeatureManager>();
bool isEnabled = featureManager
.IsEnabledAsync(nameof(CustomFeature.SQLServer))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
if (isEnabled)
{
services.AddDbContext<MangaContext>(
options => options.UseSqlServer(
@ -36,6 +43,12 @@ namespace WebApi.Modules
services.AddScoped<IAccountRepository, AccountRepository>();
}
else
{
services.AddSingleton<MangaContextFake, MangaContextFake>();
services.AddScoped<IUnitOfWork, UnitOfWorkFake>();
services.AddScoped<IAccountRepository, AccountRepositoryFake>();
}
services.AddScoped<IAccountFactory, EntityFactory>();

View File

@ -12,7 +12,7 @@ namespace WebApi.Modules
/// <summary>
/// Adds Use Cases classes.
/// </summary>
public static class ApplicationExtensions
public static class UseCasesExtensions
{
/// <summary>
/// Adds Use Cases to the ServiceCollection.

View File

@ -10,6 +10,7 @@ namespace WebApi
using Modules.Common;
using Modules.Common.FeatureFlags;
using Modules.Common.Swagger;
using Prometheus;
/// <summary>
/// Startup.
@ -29,12 +30,12 @@ namespace WebApi
public void ConfigureServices(IServiceCollection services)
{
services
.AddFeatureFlags(this.Configuration) // should be the first one.
.AddInvalidRequestLogging()
.AddCurrencyExchange(this.Configuration)
.AddPersistence(this.Configuration)
.AddSQLServer(this.Configuration)
.AddHealthChecks(this.Configuration)
.AddAuthentication(this.Configuration)
.AddFeatureFlags(this.Configuration)
.AddVersioning()
.AddSwagger()
.AddUseCases()
@ -71,7 +72,11 @@ namespace WebApi
.UseVersionedSwagger(provider, this.Configuration, env)
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints => { endpoints.MapControllers(); });
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapMetrics();
});
}
}
}

View File

@ -5,12 +5,14 @@ namespace WebApi.UseCases.V1.Accounts.CloseAccount
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Threading.Tasks;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Accounts
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -18,7 +20,8 @@ namespace WebApi.UseCases.V1.Accounts.CloseAccount
/// </see>
/// .
/// </summary>
[ApiVersion("1.0")]
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.CloseAccount)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class AccountsController : ControllerBase, IOutputPort

View File

@ -5,12 +5,14 @@ namespace WebApi.UseCases.V1.Accounts.GetAccount
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Threading.Tasks;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Accounts
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -19,6 +21,7 @@ namespace WebApi.UseCases.V1.Accounts.GetAccount
/// .
/// </summary>
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.GetAccount)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class AccountsController : ControllerBase, IOutputPort

View File

@ -4,11 +4,13 @@ namespace WebApi.UseCases.V1.Accounts.GetAccounts
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Accounts
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -17,6 +19,7 @@ namespace WebApi.UseCases.V1.Accounts.GetAccounts
/// .
/// </summary>
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.GetAccounts)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class AccountsController : ControllerBase, IOutputPort

View File

@ -5,12 +5,14 @@ namespace WebApi.UseCases.V1.Accounts.OpenAccount
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using ViewModels;
using ViewModels;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Customers
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -19,6 +21,7 @@ namespace WebApi.UseCases.V1.Accounts.OpenAccount
/// .
/// </summary>
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.OpenAccount)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class AccountsController : ControllerBase, IOutputPort

View File

@ -6,13 +6,15 @@ namespace WebApi.UseCases.V1.Transactions.Deposit
using Domain.Credits;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using ViewModels;
using ViewModels;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Accounts
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -21,6 +23,7 @@ namespace WebApi.UseCases.V1.Transactions.Deposit
/// .
/// </summary>
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.Deposit)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class TransactionsController : ControllerBase, IOutputPort

View File

@ -6,13 +6,16 @@ namespace WebApi.UseCases.V1.Transactions.Withdraw
using Domain.Debits;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Modules.Common;
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using ViewModels;
using ViewModels;
using WebApi.Modules.Common.FeatureFlags;
/// <summary>
/// Accounts
/// <see href="https://github.com/ivanpaulovich/clean-architecture-manga/wiki/Design-Patterns#controller">
@ -21,13 +24,23 @@ namespace WebApi.UseCases.V1.Transactions.Withdraw
/// .
/// </summary>
[ApiVersion("1.0")]
[FeatureGate(CustomFeature.Withdraw)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public sealed class TransactionsController : ControllerBase, IOutputPort
{
private IActionResult? _viewModel;
void IOutputPort.OutOfFunds() => this._viewModel = this.BadRequest("Out of funds.");
void IOutputPort.OutOfFunds()
{
var messages = new Dictionary<string, string[]>()
{
{ "", new [] { "Out of funds." } }
};
var problemDetails = new ValidationProblemDetails(messages);
this._viewModel = this.BadRequest(problemDetails);
}
void IOutputPort.Invalid(Notification notification)
{

View File

@ -1,4 +1,4 @@
namespace WebApi.UseCases.V2.GetAccount
namespace WebApi.UseCases.V2.Accounts.GetAccount
{
using Application.Services;
using Application.UseCases.GetAccount;
@ -21,7 +21,7 @@ namespace WebApi.UseCases.V2.GetAccount
/// </see>
/// .
/// </summary>
[FeatureGate(CustomFeature.GetAccountDetailsV2)]
[FeatureGate(CustomFeature.GetAccountV2)]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]

View File

@ -1,4 +1,4 @@
namespace WebApi.UseCases.V2.GetAccount
namespace WebApi.UseCases.V2.Accounts.GetAccount
{
using System;
using System.ComponentModel.DataAnnotations;

View File

@ -7,18 +7,26 @@
}
},
"FeatureManagement": {
"CloseAccount": true,
"Deposit": true,
"GetAccount": true,
"GetAccounts": true,
"OpenAccount": true,
"Transfer": true,
"GetAccountDetailsV2": true
"Withdraw": true,
"GetAccountV2": true,
"ErrorFilter": false,
"Swagger": true,
"Authentication": false,
"CurrencyExchange": false,
"SQLServer": true
},
"PersistenceModule": {
"UseFake": true,
"DefaultConnection": "Server=localhost;User Id=sa;Password=<YourStrong!Passw0rd>;Database=Accounts;"
},
"AuthenticationModule": {
"UseFake": true,
"AuthorityUrl": "https://wallet.local/identity-server"
},
"CurrencyExchangeModule": {
"UseFake": true
}
}

View File

@ -7,18 +7,26 @@
}
},
"FeatureManagement": {
"CloseAccount": true,
"Deposit": true,
"GetAccount": true,
"GetAccounts": true,
"OpenAccount": true,
"Transfer": true,
"GetAccountDetailsV2": true
"Withdraw": true,
"GetAccountV2": true,
"ErrorFilter": true,
"Swagger": true,
"Authentication": true,
"CurrencyExchange": true,
"SQLServer": true
},
"PersistenceModule": {
"UseFake": false,
"DefaultConnection": "Server=localhost;User Id=sa;Password=<YourStrong!Passw0rd>;Database=Accounts;"
},
"AuthenticationModule": {
"UseFake": false,
"AuthorityUrl": "https://wallet.local/identity-server"
},
"CurrencyExchangeModule": {
"UseFake": true
}
}

View File

@ -16,8 +16,8 @@ namespace ComponentTests
config.AddInMemoryCollection(
new Dictionary<string, string>
{
["PersistenceModule:UseFake"] = "true",
["CurrencyExchangeModule:UseFake"] = "true"
["FeatureManagement:SQLServer"] = "false",
["FeatureManagement:CurrencyExchangeModule"] = "false"
});
});
}

View File

@ -16,9 +16,9 @@ namespace EndToEndTests
config.AddInMemoryCollection(
new Dictionary<string, string>
{
["PersistenceModule:UseFake"] = "false",
["FeatureManagement:SQLServer"] = "true",
["PersistenceModule:DefaultConnection"] = "Server=localhost;User Id=sa;Password=<YourStrong!Passw0rd>;Database=Accounts;",
["CurrencyExchangeModule:UseFake"] = "false"
["FeatureManagement:CurrencyExchangeModule"] = "true"
});
});
}