Skip to content

Commit

Permalink
Hook up Swagger and API Versioning (#4)
Browse files Browse the repository at this point in the history
* Add user ID to AuthenticationResponse

* AssemblyAttribute ApiController

* Hook up API versioning
  • Loading branch information
xavierjohn authored Dec 24, 2022
1 parent b69f086 commit 5817e97
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 91 deletions.
7 changes: 1 addition & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
{
"rest-client.environmentVariables": {

"$shared": {},
"dev" : {
"$shared": {
"host":"https://localhost:7059"
},
"prod" : {
"host":"https://localhost:7059"
}
},
"cSpell.words": [
"Xunit"
Expand Down
14 changes: 12 additions & 2 deletions BuberDinner.Api/src/2022-12-21/DinnersController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
namespace BuberDinner.Api.Controllers;
namespace BuberDinner._2022_12_21.Controllers;

using FunctionalDDD.BuberDinner.Api;
using Asp.Versioning;
using BuberDinner.Api;
using Microsoft.AspNetCore.Mvc;

/// <summary>
/// CRUD for dinner.
/// </summary>
[ApiVersion("2022-10-01")]
public class DinnersController : ApiControllerBase
{
/// <summary>
/// Get all the dinners.
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult ListDinners()
{
return Ok(Array.Empty<string>());
Expand Down
4 changes: 3 additions & 1 deletion BuberDinner.Api/src/ApiControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

/// <summary>
/// API Base Controller.
/// </summary>
[Route("[controller]")]
[ApiController]
[Authorize]
public class ApiControllerBase : FunctionalDDDBase
{
Expand Down
5 changes: 4 additions & 1 deletion BuberDinner.Api/src/BuberDinner.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

<PropertyGroup>
<UserSecretsId>56db5788-9ea3-4265-858d-0a347e105439</UserSecretsId>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
<PackageReference Include="FunctionalDDD.Asp" />
<PackageReference Include="Mapster" />
<PackageReference Include="Mapster.DependencyInjection" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>

<ItemGroup>
Expand All @@ -17,6 +20,6 @@

<ItemGroup>
<InternalsVisibleTo Include="BuberDinner.Api.Tests" />
<AssemblyAttribute Include="Microsoft.AspNetCore.Mvc.ApiController" />
</ItemGroup>

</Project>
89 changes: 89 additions & 0 deletions BuberDinner.Api/src/ConfigureSwaggerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
namespace BuberDinner.Api;

using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text;

/// <summary>
/// Configures the Swagger generation options.
/// </summary>
/// <remarks>This allows API versioning to define a Swagger document per API version after the
/// <see cref="IApiVersionDescriptionProvider"/> service has been resolved from the service container.</remarks>
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider provider;

/// <summary>
/// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
/// </summary>
/// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;

/// <inheritdoc />
public void Configure(SwaggerGenOptions options)
{
// add a swagger document for each discovered API version
// note: you might choose to skip or document deprecated API versions differently
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}

private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var text = new StringBuilder("Buber Dinner the AirBnB for dinner.");
var info = new OpenApiInfo()
{
Title = "Buber Dinner",
Version = description.ApiVersion.ToString(),
Contact = new OpenApiContact() { Name = "Xavier John", Email = "xavier@somewhere.com" },
License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
};

if (description.IsDeprecated)
{
text.Append(" This API version has been deprecated.");
}

if (description.SunsetPolicy is SunsetPolicy policy)
{
if (policy.Date is DateTimeOffset when)
{
text.Append(" The API will be sunset on ")
.Append(when.Date.ToShortDateString())
.Append('.');
}

if (policy.HasLinks)
{
text.AppendLine();

for (var i = 0; i < policy.Links.Count; i++)
{
var link = policy.Links[i];

if (link.Type == "text/html")
{
text.AppendLine();

if (link.Title.HasValue)
{
text.Append(link.Title.Value).Append(": ");
}

text.Append(link.LinkTarget.OriginalString);
}
}
}
}

info.Description = text.ToString();

return info;
}
}
26 changes: 25 additions & 1 deletion BuberDinner.Api/src/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,37 @@
using Mapster;
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;

public static class DependencyInjection
internal static class DependencyInjection
{
public static IServiceCollection AddPresentation(this IServiceCollection services)
{
services.AddControllers();
services.AddMappings();
services.AddApiVersioning(
options =>
{
options.ReportApiVersions = true;
})
.AddMvc()
.AddApiExplorer();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(
options =>
{
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();

var fileName = typeof(Program).Assembly.GetName().Name + ".xml";
var filePath = Path.Combine(AppContext.BaseDirectory, fileName);

// integrate XML comments
options.IncludeXmlComments(filePath);
});
return services;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
namespace BuberDinner.Api.Netural.Controllers;

using Asp.Versioning;
using BuberDinner.Api.Netural.Models.Authentication;
using MapsterMapper;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

/// <summary>
/// Authentication Controller
/// </summary>
[AllowAnonymous]
[ApiVersionNeutral]
public class AuthenticationController : ApiControllerBase
{
private readonly ISender _sender;
private readonly IMapper _mapper;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="sender"></param>
/// <param name="mapper"></param>
public AuthenticationController(ISender sender, IMapper mapper)
{
_sender = sender;
_mapper = mapper;
}

/// <summary>
/// Register a new user.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("register")]
public async Task<ActionResult<AuthenticationResponse>> Register(RegisterRequest request) =>
await request.ToRegisterCommand()
.BindAsync(command => _sender.Send(command))
.MapAsync(authResult => _mapper.Map<AuthenticationResponse>(authResult))
.MapAsync(_mapper.Map<AuthenticationResponse>)
.FinallyAsync(result => MapToActionResult(result));

/// <summary>
/// Login for existing user.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("login")]
public async Task<ActionResult<AuthenticationResponse>> Login(LoginRequest request) =>
await request.ToLoginQuery()
.BindAsync(command => _sender.Send(command))
.MapAsync(authResult => _mapper.Map<AuthenticationResponse>(authResult))
.MapAsync(_mapper.Map<AuthenticationResponse>)
.FinallyAsync(result => MapToActionResult(result));
}
29 changes: 19 additions & 10 deletions BuberDinner.Api/src/Netural/Controllers/ErrorController.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
namespace FunctionalDDD.BuberDinner.Api.Netural.Controllers
{
using Microsoft.AspNetCore.Mvc;
namespace BuberDinner.Api.Netural.Controllers;
using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;

[ApiController]
public class ErrorController : ControllerBase
/// <summary>
/// Unhandled error controller.
/// </summary>
[ApiVersionNeutral]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : ControllerBase
{
/// <summary>
/// Show error
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("/error")]
public IActionResult Error()
{
[Route("/error")]
public IActionResult Error()
{
return Problem();
}
return Problem();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
using BuberDinner.Application.Services.Authentication.Common;
using Mapster;

public class AuthenticationMappingConfig : IRegister
internal class AuthenticationMappingConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<AuthenticationResult, AuthenticationResponse>()
.Map(dest => dest.Token, src => src.Token)
.Map(dest => dest.UserId, src => src.User.Id)
.Map(dest => dest, src => src.User);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
namespace BuberDinner.Api.Netural.Models.Authentication;

/// <summary>
/// Authentication Response
/// </summary>
public class AuthenticationResponse
{
//public Guid Id { get; set; }
/// <summary>
/// User Id
/// </summary>
public string? UserId { get; set; }


/// <summary>
/// First Name
/// </summary>
public string? FirstName { get; set; }

/// <summary>
/// Last Name
/// </summary>
public string? LastName { get; set; }

/// <summary>
/// Email address
/// </summary>
public string? Email { get; set; }

/// <summary>
/// Token
/// </summary>
public string? Token { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
using BuberDinner.Application.Services.Authentication.Queries;
using BuberDinner.Domain.User.ValueObjects;

/// <summary>
/// Login request model.
/// </summary>
/// <param name="email">Email address</param>
/// <param name="password">Password</param>
public record LoginRequest(
string email,
string password
)
{
public Result<LoginQuery> ToLoginQuery() =>
internal Result<LoginQuery> ToLoginQuery() =>
EmailAddress.Create(email)
.Combine(Password.Create(password))
.Bind((email, pwd) => LoginQuery.Create(email, pwd));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
using BuberDinner.Application.Services.Authentication.Commands;
using BuberDinner.Domain.User.ValueObjects;

/// <summary>
/// Register request model.
/// </summary>
/// <param name="firstName">First Name</param>
/// <param name="lastName">Last Name</param>
/// <param name="email">Email address</param>
/// <param name="password">Password</param>
public record RegisterRequest(
string firstName,
string lastName,
Expand All @@ -11,7 +18,7 @@ string password
)
{

public Result<RegisterCommand> ToRegisterCommand() =>
internal Result<RegisterCommand> ToRegisterCommand() =>
FirstName.Create(firstName)
.Combine(LastName.Create(lastName))
.Combine(EmailAddress.Create(email))
Expand Down
Loading

0 comments on commit 5817e97

Please sign in to comment.