feat(dir): migrate OpenAPI & SwaggerUI

This commit is contained in:
Collin Barrett 2024-06-04 10:53:21 -05:00
parent 07343fa0d9
commit e0647a1303
7 changed files with 254 additions and 30 deletions

View file

@ -1,6 +1,9 @@
using FilterLists.Directory.Application.Queries;
using MediatR;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using static FilterLists.Directory.Api.OpenApi.OpenApiTags;
using static FilterLists.Directory.Application.Queries.GetListDetails;
namespace FilterLists.Directory.Api;
@ -10,47 +13,106 @@ internal static class Endpoints
internal static void MapEndpoints(this WebApplication app)
{
app.MapGet("/languages",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLanguages.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLanguages.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [LanguagesTag],
Summary = "Gets the languages targeted by the FilterLists.",
OperationId = nameof(GetLanguages)
});
app.MapGet("/licenses",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLicenses.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLicenses.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [LicensesTag],
Summary = "Gets the licenses applied to the FilterLists.",
OperationId = nameof(GetLicenses)
});
app.MapGet("/lists",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLists.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetLists.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [FilterListsTag],
Summary = "Gets the FilterLists.",
OperationId = nameof(GetLists)
});
app.MapGet("/lists/{id:int}", async Task<Results<Ok<ListDetailsVm>, NotFound>>
(int id, IMediator mediator, CancellationToken ct) =>
{
var list = await mediator.Send(new Request(id), ct);
return list is not null
? TypedResults.Ok(list)
: TypedResults.NotFound();
});
(int id, IMediator mediator, CancellationToken ct) =>
{
var list = await mediator.Send(new Request(id), ct);
return list is not null
? TypedResults.Ok(list)
: TypedResults.NotFound();
})
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [FilterListsTag],
Summary = "Gets the details of the FilterList.",
OperationId = nameof(GetListDetails),
Parameters =
[
new OpenApiParameter
{
Name = "id",
In = ParameterLocation.Path,
Description = "The identifier of the FilterList.",
Required = true,
Example = new OpenApiInteger(1)
}
]
});
app.MapGet("/maintainers",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetMaintainers.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetMaintainers.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [MaintainersTag],
Summary = "Gets the maintainers of the FilterLists.",
OperationId = nameof(GetMaintainers)
});
app.MapGet("/software",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetSoftware.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetSoftware.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [SoftwareTag],
Summary = "Gets the software that subscribes to the FilterLists.",
OperationId = nameof(GetSoftware)
});
app.MapGet("/syntaxes",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetSyntaxes.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetSyntaxes.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [SyntaxesTag],
Summary = "Gets the syntaxes of the FilterLists.",
OperationId = nameof(GetSyntaxes)
});
app.MapGet("/tags",
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetTags.Request(), ct)
);
(IMediator mediator, CancellationToken ct) =>
mediator.CreateStream(new GetTags.Request(), ct)
)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Tags = [TagsTag],
Summary = "Gets the tags of the FilterLists.",
OperationId = nameof(GetTags)
});
}
}

View file

@ -11,4 +11,9 @@
<ProjectReference Include="..\FilterLists.Directory.Application\FilterLists.Directory.Application.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
</ItemGroup>
</Project>

View file

@ -0,0 +1,38 @@
using Microsoft.OpenApi.Models;
using ConfigurationExtensions = FilterLists.Directory.Application.ConfigurationExtensions;
namespace FilterLists.Directory.Api.OpenApi;
internal static class OpenApiConfigurationExtensions
{
internal static void AddOpenApiGen(this IServiceCollection services)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc(
"v1",
new OpenApiInfo
{
Title = "FilterLists Directory API",
Description = "An ASP.NET Core API serving the core FilterList information.",
Version = "v1",
TermsOfService =
new Uri("https://github.com/collinbarrett/FilterLists/blob/main/.github/CODE_OF_CONDUCT.md"),
Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") },
License = new OpenApiLicense
{
Name = "MIT License",
Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/main/LICENSE")
}
});
// include view model xml comments
var xmlFilename = $"{typeof(ConfigurationExtensions).Assembly.GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
// include OpenApiTag Description and ExternalDocs
options.DocumentFilter<OpenApiTags.TagDescriptionsDocumentFilter>();
});
}
}

View file

@ -0,0 +1,106 @@
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace FilterLists.Directory.Api.OpenApi;
internal static class OpenApiTags
{
internal static readonly OpenApiTag LanguagesTag = new()
{
Name = nameof(QueryDbContext.Languages),
Description = "A written form of communication used by sites targeted by a FilterList",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(Language)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Language)}")
}
};
internal static readonly OpenApiTag LicensesTag = new()
{
Name = nameof(QueryDbContext.Licenses),
Description = "A legal document governing the use or redistribution of a FilterList",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(License)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(License)}")
}
};
internal static readonly OpenApiTag FilterListsTag = new()
{
Name = nameof(QueryDbContext.FilterLists),
Description = "A text file containing a list of rules for blocking or manipulating internet traffic",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(FilterList)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(FilterList)}")
}
};
internal static readonly OpenApiTag MaintainersTag = new()
{
Name = nameof(QueryDbContext.Maintainers),
Description = "An individual, group, or organization who maintains one or more FilterLists",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(Maintainer)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Maintainer)}")
}
};
internal static readonly OpenApiTag SoftwareTag = new()
{
Name = nameof(QueryDbContext.Software),
Description = "An application, browser extension, or other utility that consumes FilterLists",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(Software)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Software)}")
}
};
internal static readonly OpenApiTag SyntaxesTag = new()
{
Name = nameof(QueryDbContext.Syntaxes),
Description = "A named set of rules that govern the format of a FilterList",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(Syntax)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Syntax)}")
}
};
internal static readonly OpenApiTag TagsTag = new()
{
Name = nameof(QueryDbContext.Tags),
Description =
"A generic taxonomy applied to a FilterList to provide information about its contents and/or purpose",
ExternalDocs = new OpenApiExternalDocs
{
Description = $"{nameof(FilterLists)} {nameof(Tag)} Wiki",
Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Tag)}")
}
};
[UsedImplicitly]
internal class TagDescriptionsDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Tags =
[
LanguagesTag,
LicensesTag,
FilterListsTag,
MaintainersTag,
SoftwareTag,
SyntaxesTag,
TagsTag
];
}
}
}

View file

@ -1,4 +1,5 @@
using FilterLists.Directory.Api;
using FilterLists.Directory.Api.OpenApi;
using FilterLists.Directory.Application;
var builder = WebApplication.CreateBuilder(args);
@ -6,6 +7,7 @@
builder.WebHost.ConfigureKestrel(serverOptions => serverOptions.AddServerHeader = false);
builder.AddServiceDefaults();
builder.Services.AddProblemDetails();
builder.Services.AddOpenApiGen();
builder.AddApplication();
var app = builder.Build();
@ -13,5 +15,7 @@
app.UseExceptionHandler();
app.MapEndpoints();
app.MapDefaultEndpoints();
app.UseSwagger();
app.UseSwaggerUI();
app.Run();

View file

@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "lists",
"launchUrl": "swagger/index.html",
"applicationUrl": "http://localhost:5444",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
@ -15,7 +15,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "lists",
"launchUrl": "swagger/index.html",
"applicationUrl": "https://localhost:7490;http://localhost:5444",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"

View file

@ -4,6 +4,15 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<NoWarn>1591;1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>1591;1701;1702</NoWarn>
</PropertyGroup>
<ItemGroup>