First commit. Include junk from template to remove

This commit is contained in:
Dominic Villemure
2024-03-09 20:25:30 -05:00
commit bbcefcf76f
140 changed files with 8151 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
using Azure.Identity;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Infrastructure.Data;
using Hutopy.Web.Services;
using Microsoft.AspNetCore.Mvc;
using NSwag;
using NSwag.Generation.Processors.Security;
namespace Microsoft.Extensions.DependencyInjection;
public static class DependencyInjection
{
public static IServiceCollection AddWebServices(this IServiceCollection services)
{
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddScoped<IUser, CurrentUser>();
services.AddHttpContextAccessor();
services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>();
services.AddExceptionHandler<CustomExceptionHandler>();
services.AddRazorPages();
// Customise default API behaviour
services.Configure<ApiBehaviorOptions>(options =>
options.SuppressModelStateInvalidFilter = true);
services.AddEndpointsApiExplorer();
services.AddOpenApiDocument((configure, sp) =>
{
configure.Title = "Hutopy API";
// Add JWT
configure.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
In = OpenApiSecurityApiKeyLocation.Header,
Description = "Type into the textbox: Bearer {your JWT token}."
});
configure.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT"));
});
return services;
}
public static IServiceCollection AddKeyVaultIfConfigured(this IServiceCollection services, ConfigurationManager configuration)
{
var keyVaultUri = configuration["KeyVaultUri"];
if (!string.IsNullOrWhiteSpace(keyVaultUri))
{
configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential());
}
return services;
}
}

View File

@@ -0,0 +1,52 @@
using Hutopy.Application.Common.Models;
using Hutopy.Application.TodoItems.Commands.CreateTodoItem;
using Hutopy.Application.TodoItems.Commands.DeleteTodoItem;
using Hutopy.Application.TodoItems.Commands.UpdateTodoItem;
using Hutopy.Application.TodoItems.Commands.UpdateTodoItemDetail;
using Hutopy.Application.TodoItems.Queries.GetTodoItemsWithPagination;
namespace Hutopy.Web.Endpoints;
public class TodoItems : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.RequireAuthorization()
.MapGet(GetTodoItemsWithPagination)
.MapPost(CreateTodoItem)
.MapPut(UpdateTodoItem, "{id}")
.MapPut(UpdateTodoItemDetail, "UpdateDetail/{id}")
.MapDelete(DeleteTodoItem, "{id}");
}
public Task<PaginatedList<TodoItemBriefDto>> GetTodoItemsWithPagination(ISender sender, [AsParameters] GetTodoItemsWithPaginationQuery query)
{
return sender.Send(query);
}
public Task<int> CreateTodoItem(ISender sender, CreateTodoItemCommand command)
{
return sender.Send(command);
}
public async Task<IResult> UpdateTodoItem(ISender sender, int id, UpdateTodoItemCommand command)
{
if (id != command.Id) return Results.BadRequest();
await sender.Send(command);
return Results.NoContent();
}
public async Task<IResult> UpdateTodoItemDetail(ISender sender, int id, UpdateTodoItemDetailCommand command)
{
if (id != command.Id) return Results.BadRequest();
await sender.Send(command);
return Results.NoContent();
}
public async Task<IResult> DeleteTodoItem(ISender sender, int id)
{
await sender.Send(new DeleteTodoItemCommand(id));
return Results.NoContent();
}
}

View File

@@ -0,0 +1,42 @@
using Hutopy.Application.TodoLists.Commands.CreateTodoList;
using Hutopy.Application.TodoLists.Commands.DeleteTodoList;
using Hutopy.Application.TodoLists.Commands.UpdateTodoList;
using Hutopy.Application.TodoLists.Queries.GetTodos;
namespace Hutopy.Web.Endpoints;
public class TodoLists : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.RequireAuthorization()
.MapGet(GetTodoLists)
.MapPost(CreateTodoList)
.MapPut(UpdateTodoList, "{id}")
.MapDelete(DeleteTodoList, "{id}");
}
public Task<TodosVm> GetTodoLists(ISender sender)
{
return sender.Send(new GetTodosQuery());
}
public Task<int> CreateTodoList(ISender sender, CreateTodoListCommand command)
{
return sender.Send(command);
}
public async Task<IResult> UpdateTodoList(ISender sender, int id, UpdateTodoListCommand command)
{
if (id != command.Id) return Results.BadRequest();
await sender.Send(command);
return Results.NoContent();
}
public async Task<IResult> DeleteTodoList(ISender sender, int id)
{
await sender.Send(new DeleteTodoListCommand(id));
return Results.NoContent();
}
}

View File

@@ -0,0 +1,12 @@
using Hutopy.Infrastructure.Identity;
namespace Hutopy.Web.Endpoints;
public class Users : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.MapIdentityApi<ApplicationUser>();
}
}

View File

@@ -0,0 +1,18 @@
using Hutopy.Application.WeatherForecasts.Queries.GetWeatherForecasts;
namespace Hutopy.Web.Endpoints;
public class WeatherForecasts : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.RequireAuthorization()
.MapGet(GetWeatherForecasts);
}
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecasts(ISender sender)
{
return await sender.Send(new GetWeatherForecastsQuery());
}
}

3
src/Web/GlobalUsings.cs Normal file
View File

@@ -0,0 +1,3 @@
global using Ardalis.GuardClauses;
global using Hutopy.Web.Infrastructure;
global using MediatR;

View File

@@ -0,0 +1,87 @@
using Hutopy.Application.Common.Exceptions;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Infrastructure;
public class CustomExceptionHandler : IExceptionHandler
{
private readonly Dictionary<Type, Func<HttpContext, Exception, Task>> _exceptionHandlers;
public CustomExceptionHandler()
{
// Register known exception types and handlers.
_exceptionHandlers = new()
{
{ typeof(ValidationException), HandleValidationException },
{ typeof(NotFoundException), HandleNotFoundException },
{ typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
{ typeof(ForbiddenAccessException), HandleForbiddenAccessException },
};
}
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var exceptionType = exception.GetType();
if (_exceptionHandlers.ContainsKey(exceptionType))
{
await _exceptionHandlers[exceptionType].Invoke(httpContext, exception);
return true;
}
return false;
}
private async Task HandleValidationException(HttpContext httpContext, Exception ex)
{
var exception = (ValidationException)ex;
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
await httpContext.Response.WriteAsJsonAsync(new ValidationProblemDetails(exception.Errors)
{
Status = StatusCodes.Status400BadRequest,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
});
}
private async Task HandleNotFoundException(HttpContext httpContext, Exception ex)
{
var exception = (NotFoundException)ex;
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails()
{
Status = StatusCodes.Status404NotFound,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
});
}
private async Task HandleUnauthorizedAccessException(HttpContext httpContext, Exception ex)
{
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = StatusCodes.Status401Unauthorized,
Title = "Unauthorized",
Type = "https://tools.ietf.org/html/rfc7235#section-3.1"
});
}
private async Task HandleForbiddenAccessException(HttpContext httpContext, Exception ex)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = StatusCodes.Status403Forbidden,
Title = "Forbidden",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3"
});
}
}

View File

@@ -0,0 +1,6 @@
namespace Hutopy.Web.Infrastructure;
public abstract class EndpointGroupBase
{
public abstract void Map(WebApplication app);
}

View File

@@ -0,0 +1,46 @@
using System.Diagnostics.CodeAnalysis;
namespace Hutopy.Web.Infrastructure;
public static class IEndpointRouteBuilderExtensions
{
public static IEndpointRouteBuilder MapGet(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
{
Guard.Against.AnonymousMethod(handler);
builder.MapGet(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapPost(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
{
Guard.Against.AnonymousMethod(handler);
builder.MapPost(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapPut(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern)
{
Guard.Against.AnonymousMethod(handler);
builder.MapPut(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapDelete(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern)
{
Guard.Against.AnonymousMethod(handler);
builder.MapDelete(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
}

View File

@@ -0,0 +1,18 @@
using System.Reflection;
namespace Hutopy.Web.Infrastructure;
public static class MethodInfoExtensions
{
public static bool IsAnonymous(this MethodInfo method)
{
var invalidChars = new[] { '<', '>' };
return method.Name.Any(invalidChars.Contains);
}
public static void AnonymousMethod(this IGuardClause guardClause, Delegate input)
{
if (input.Method.IsAnonymous())
throw new ArgumentException("The endpoint name must be specified when using anonymous handlers.");
}
}

View File

@@ -0,0 +1,37 @@
using System.Reflection;
namespace Hutopy.Web.Infrastructure;
public static class WebApplicationExtensions
{
public static RouteGroupBuilder MapGroup(this WebApplication app, EndpointGroupBase group)
{
var groupName = group.GetType().Name;
return app
.MapGroup($"/api/{groupName}")
.WithGroupName(groupName)
.WithTags(groupName)
.WithOpenApi();
}
public static WebApplication MapEndpoints(this WebApplication app)
{
var endpointGroupType = typeof(EndpointGroupBase);
var assembly = Assembly.GetExecutingAssembly();
var endpointGroupTypes = assembly.GetExportedTypes()
.Where(t => t.IsSubclassOf(endpointGroupType));
foreach (var type in endpointGroupTypes)
{
if (Activator.CreateInstance(type) is EndpointGroupBase instance)
{
instance.Map(app);
}
}
return app;
}
}

View File

@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@@ -0,0 +1,25 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Hutopy.Web.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

View File

@@ -0,0 +1,36 @@
@using Hutopy.Infrastructure.Identity
@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@{
string? returnUrl = null;
var query = ViewContext.HttpContext.Request.Query;
if (query.ContainsKey("returnUrl"))
{
returnUrl = query["returnUrl"];
}
}
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity!.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a>
</li>
}
</ul>

View File

@@ -0,0 +1,3 @@
@using Hutopy.Web
@namespace Hutopy.Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

64
src/Web/Program.cs Normal file
View File

@@ -0,0 +1,64 @@
using Hutopy.Infrastructure.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Add services to the container.
builder.Services.AddKeyVaultIfConfigured(builder.Configuration);
builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddWebServices();
var app = builder.Build();
app.UseCors("AllowAll");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
await app.InitialiseDatabaseAsync();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHealthChecks("/health");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSwaggerUi(settings =>
{
settings.Path = "/api";
settings.DocumentPath = "/api/specification.json";
});
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();
app.MapFallbackToFile("index.html");
app.UseExceptionHandler(options => { });
app.Map("/", () => Results.Redirect("/api"));
app.MapEndpoints();
app.Run();
public partial class Program { }

View File

@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61846",
"sslPort": 44312
}
},
"profiles": {
"Hutopy.Web": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Security.Claims;
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Web.Services;
public class CurrentUser : IUser
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUser(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string? Id => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
}

44
src/Web/Web.csproj Normal file
View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>Hutopy.Web</RootNamespace>
<AssemblyName>Hutopy.Web</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" />
<PackageReference Include="NSwag.AspNetCore" />
<PackageReference Include="NSwag.MSBuild">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentValidation.AspNetCore" />
</ItemGroup>
<!-- Auto-generated Open API specification and Angular TypeScript clients -->
<PropertyGroup>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
</PropertyGroup>
<Target Name="NSwag" AfterTargets="PostBuildEvent" Condition=" '$(Configuration)' == 'Debug' And '$(SkipNSwag)' != 'True' ">
<Exec ConsoleToMSBuild="true" ContinueOnError="true" WorkingDirectory="$(ProjectDir)" EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="$(NSwagExe_Net80) run config.nswag /variables:Configuration=$(Configuration)">
<Output TaskParameter="ExitCode" PropertyName="NSwagExitCode" />
<Output TaskParameter="ConsoleOutput" PropertyName="NSwagOutput" />
</Exec>
<Message Text="$(NSwagOutput)" Condition="'$(NSwagExitCode)' == '0'" Importance="low" />
<Error Text="$(NSwagOutput)" Condition="'$(NSwagExitCode)' != '0'" />
</Target>
</Project>

139
src/Web/Web.http Normal file
View File

@@ -0,0 +1,139 @@
# For more info on HTTP files go to https://aka.ms/vs/httpfile
@Web_HostAddress = https://localhost:5001
@Email=administrator@localhost
@Password=Administrator1!
@BearerToken=<YourToken>
# POST Users Register
POST {{Web_HostAddress}}/api/Users/Register
Content-Type: application/json
{
"email": "{{Email}}",
"password": "{{Password}}"
}
###
# POST Users Login
POST {{Web_HostAddress}}/api/Users/Login
Content-Type: application/json
{
"email": "{{Email}}",
"password": "{{Password}}"
}
###
# POST Users Refresh
POST {{Web_HostAddress}}/api/Users/Refresh
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
{
"refreshToken": ""
}
###
# GET WeatherForecast
GET {{Web_HostAddress}}/api/WeatherForecasts
Authorization: Bearer {{BearerToken}}
###
# GET TodoLists
GET {{Web_HostAddress}}/api/TodoLists
Authorization: Bearer {{BearerToken}}
###
# POST TodoLists
POST {{Web_HostAddress}}/api/TodoLists
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
// CreateTodoListCommand
{
"Title": "Backlog"
}
###
# PUT TodoLists
PUT {{Web_HostAddress}}/api/TodoLists/1
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
// UpdateTodoListCommand
{
"Id": 1,
"Title": "Product Backlog"
}
###
# DELETE TodoLists
DELETE {{Web_HostAddress}}/api/TodoLists/1
Authorization: Bearer {{BearerToken}}
###
# GET TodoItems
@PageNumber = 1
@PageSize = 10
GET {{Web_HostAddress}}/api/TodoItems?ListId=1&PageNumber={{PageNumber}}&PageSize={{PageSize}}
Authorization: Bearer {{BearerToken}}
###
# POST TodoItems
POST {{Web_HostAddress}}/api/TodoItems
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
// CreateTodoItemCommand
{
"ListId": 1,
"Title": "Eat a burrito 🌯"
}
###
#PUT TodoItems UpdateItemDetails
PUT {{Web_HostAddress}}/api/TodoItems/UpdateItemDetails?Id=1
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
// UpdateTodoItemDetailCommand
{
"Id": 1,
"ListId": 1,
"Priority": 3,
"Note": "This is a good idea!"
}
###
# PUT TodoItems
PUT {{Web_HostAddress}}/api/TodoItems/1
Authorization: Bearer {{BearerToken}}
Content-Type: application/json
// UpdateTodoItemCommand
{
"Id": 1,
"Title": "Eat a yummy burrito 🌯",
"Done": true
}
###
# DELETE TodoItem
DELETE {{Web_HostAddress}}/api/TodoItems/1
Authorization: Bearer {{BearerToken}}
###

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.AspNetCore.SpaProxy": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

13
src/Web/appsettings.json Normal file
View File

@@ -0,0 +1,13 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost,1433;Database=TestDeux;User Id=sa;Password={DB_PASSWORD};MultipleActiveResultSets=true;TrustServerCertificate=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

63
src/Web/config.nswag Normal file
View File

@@ -0,0 +1,63 @@
{
"runtime": "Net80",
"defaultVariables": null,
"documentGenerator": {
"aspNetCoreToOpenApi": {
"project": "Web.csproj",
"msBuildProjectExtensionsPath": null,
"configuration": null,
"runtime": null,
"targetFramework": null,
"noBuild": true,
"msBuildOutputPath": null,
"verbose": false,
"workingDirectory": null,
"requireParametersWithoutDefault": true,
"apiGroupNames": null,
"defaultPropertyNameHandling": "CamelCase",
"defaultReferenceTypeNullHandling": "Null",
"defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
"defaultResponseReferenceTypeNullHandling": "NotNull",
"generateOriginalParameterNames": true,
"defaultEnumHandling": "Integer",
"flattenInheritanceHierarchy": false,
"generateKnownTypes": true,
"generateEnumMappingDescription": false,
"generateXmlObjects": false,
"generateAbstractProperties": false,
"generateAbstractSchemas": true,
"ignoreObsoleteProperties": false,
"allowReferencesWithProperties": false,
"useXmlDocumentation": true,
"resolveExternalXmlDocumentation": true,
"excludedTypeNames": [],
"serviceHost": null,
"serviceBasePath": null,
"serviceSchemes": [],
"infoTitle": "Hutopy API",
"infoDescription": null,
"infoVersion": "1.0.0",
"documentTemplate": null,
"documentProcessorTypes": [],
"operationProcessorTypes": [],
"typeNameGeneratorType": null,
"schemaNameGeneratorType": null,
"contractResolverType": null,
"serializerSettingsType": null,
"useDocumentProvider": true,
"documentName": "v1",
"aspNetCoreEnvironment": null,
"createWebHostBuilderMethod": null,
"startupType": null,
"allowNullableBodyParameters": true,
"useHttpAttributeNameAsOperationId": false,
"output": "wwwroot/api/specification.json",
"outputType": "OpenApi3",
"newLineBehavior": "Auto",
"assemblyPaths": [],
"assemblyConfig": null,
"referencePaths": [],
"useNuGetCache": false
}
}
}

File diff suppressed because it is too large Load Diff

BIN
src/Web/wwwroot/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB