added rate limiting middleware and configured it. added quotes data in json format. added method to seeder to seed database with quote data.
This commit is contained in:
@@ -24,7 +24,7 @@ namespace server.Controllers.v1
|
|||||||
/// <response code="200">Returns the collection of episodes requested.</response>
|
/// <response code="200">Returns the collection of episodes requested.</response>
|
||||||
/// <response code="400">Not a valid request.</response>
|
/// <response code="400">Not a valid request.</response>
|
||||||
/// <response code="500">Failed to get episodes.</response>
|
/// <response code="500">Failed to get episodes.</response>
|
||||||
/// <returns>A collection of episodes.</returns>
|
/// <returns>Returns a collection of episodes.</returns>
|
||||||
[MapToApiVersion("1.0")]
|
[MapToApiVersion("1.0")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(typeof(List<Episode>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(List<Episode>), StatusCodes.Status200OK)]
|
||||||
@@ -52,7 +52,7 @@ namespace server.Controllers.v1
|
|||||||
/// <response code="400">Not a valid request.</response>
|
/// <response code="400">Not a valid request.</response>
|
||||||
/// <response code="404">Unable to find an episode with the provided number.</response>
|
/// <response code="404">Unable to find an episode with the provided number.</response>
|
||||||
/// <response code="500">Failed to get episode.</response>
|
/// <response code="500">Failed to get episode.</response>
|
||||||
/// <returns>The episode requested.</returns>
|
/// <returns>Returns the episode requested.</returns>
|
||||||
[MapToApiVersion("1.0")]
|
[MapToApiVersion("1.0")]
|
||||||
[HttpGet("{number:int}")]
|
[HttpGet("{number:int}")]
|
||||||
[ProducesResponseType(typeof(Episode), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(Episode), StatusCodes.Status200OK)]
|
||||||
@@ -65,9 +65,9 @@ namespace server.Controllers.v1
|
|||||||
{
|
{
|
||||||
var episode = await _episodeRepository.GetEpisodeByNumberAsync(number);
|
var episode = await _episodeRepository.GetEpisodeByNumberAsync(number);
|
||||||
|
|
||||||
if (episode == null) return Problem(detail: $"Could not find episode {number}", statusCode: 404);
|
return episode == null ?
|
||||||
|
Problem(detail: $"Could not find episode {number}", statusCode: 404) :
|
||||||
return Ok(episode);
|
Ok(episode);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ namespace server.Controllers.v1
|
|||||||
_seasonRepository = seasonRepository;
|
_seasonRepository = seasonRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a collection of seasons.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">Returns the collection of seasons requested.</response>
|
||||||
|
/// <response code="500">Failed to get seasons.</response>
|
||||||
|
/// <returns>Returns a collection of seasons.</returns>
|
||||||
[MapToApiVersion("1.0")]
|
[MapToApiVersion("1.0")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(typeof(List<Season>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(List<Season>), StatusCodes.Status200OK)]
|
||||||
@@ -35,10 +41,16 @@ namespace server.Controllers.v1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a season by its number.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">Returns the season requested.</response>
|
||||||
|
/// <response code="404">Could not find a seaon with number provided.</response>
|
||||||
|
/// <response code="500">Failed to get season.</response>
|
||||||
|
/// <returns>Returns the season requested</returns>
|
||||||
[MapToApiVersion("1.0")]
|
[MapToApiVersion("1.0")]
|
||||||
[HttpGet("{number:int}")]
|
[HttpGet("{number:int}")]
|
||||||
[ProducesResponseType(typeof(Season), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(Season), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
|
||||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
|
||||||
public async Task<ActionResult<Season>> GetSeasonByNumberAsync(int number)
|
public async Task<ActionResult<Season>> GetSeasonByNumberAsync(int number)
|
||||||
@@ -47,9 +59,9 @@ namespace server.Controllers.v1
|
|||||||
{
|
{
|
||||||
var season = await _seasonRepository.GetSeasonByNumberAsync(number);
|
var season = await _seasonRepository.GetSeasonByNumberAsync(number);
|
||||||
|
|
||||||
if (season == null) return Problem(detail: $"Could not find season {number}", statusCode: 404);
|
return season == null ?
|
||||||
|
Problem(detail: $"Could not find season {number}", statusCode: 404) :
|
||||||
return Ok(season);
|
Ok(season);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace server.Options
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private OpenApiInfo CreateVersionInfo(ApiVersionDescription description)
|
private static OpenApiInfo CreateVersionInfo(ApiVersionDescription description)
|
||||||
{
|
{
|
||||||
var info = new OpenApiInfo
|
var info = new OpenApiInfo
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ namespace server.Persistence.Seed
|
|||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IMongoCollection<Season> _seasons;
|
private readonly IMongoCollection<Season> _seasons;
|
||||||
private readonly IMongoCollection<Episode> _episodes;
|
private readonly IMongoCollection<Episode> _episodes;
|
||||||
|
private readonly IMongoCollection<Quote> _quotes;
|
||||||
|
|
||||||
public Seeder()
|
public Seeder()
|
||||||
{
|
{
|
||||||
@@ -22,15 +23,16 @@ namespace server.Persistence.Seed
|
|||||||
_database = _client.GetDatabase(_config.GetSection("MongoDBSettings:DatabaseName").Value);
|
_database = _client.GetDatabase(_config.GetSection("MongoDBSettings:DatabaseName").Value);
|
||||||
_seasons = _database.GetCollection<Season>(_config.GetSection("MongoDBSettings:SeasonsCollection").Value);
|
_seasons = _database.GetCollection<Season>(_config.GetSection("MongoDBSettings:SeasonsCollection").Value);
|
||||||
_episodes = _database.GetCollection<Episode>(_config.GetSection("MongoDBSettings:EpisodesCollection").Value);
|
_episodes = _database.GetCollection<Episode>(_config.GetSection("MongoDBSettings:EpisodesCollection").Value);
|
||||||
|
_quotes = _database.GetCollection<Quote>(_config.GetSection("MongoDBSettings:QuotesCollection").Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SeedSeasonsAsync()
|
public async Task SeedSeasonsAsync()
|
||||||
{
|
{
|
||||||
await _seasons.DeleteManyAsync(season => true);
|
await _seasons.DeleteManyAsync(season => true);
|
||||||
|
|
||||||
var fileName = "seasons.json";
|
const string fileName = "seasons.json";
|
||||||
var seasonsFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
|
var seasonsFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
|
||||||
var seasonsJson = File.ReadAllText(seasonsFilePath);
|
var seasonsJson = await File.ReadAllTextAsync(seasonsFilePath);
|
||||||
var seasons = JsonSerializer.Deserialize<List<Season>>(seasonsJson);
|
var seasons = JsonSerializer.Deserialize<List<Season>>(seasonsJson);
|
||||||
|
|
||||||
if(seasons != null)
|
if(seasons != null)
|
||||||
@@ -44,9 +46,9 @@ namespace server.Persistence.Seed
|
|||||||
{
|
{
|
||||||
await _episodes.DeleteManyAsync(episode => true);
|
await _episodes.DeleteManyAsync(episode => true);
|
||||||
|
|
||||||
var fileName = "episodes.json";
|
const string fileName = "episodes.json";
|
||||||
var episodesFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
|
var episodesFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
|
||||||
var episodesJson = File.ReadAllText(episodesFilePath);
|
var episodesJson = await File.ReadAllTextAsync(episodesFilePath);
|
||||||
var episodes = JsonSerializer.Deserialize<List<Episode>>(episodesJson);
|
var episodes = JsonSerializer.Deserialize<List<Episode>>(episodesJson);
|
||||||
|
|
||||||
if (episodes != null)
|
if (episodes != null)
|
||||||
@@ -55,5 +57,22 @@ namespace server.Persistence.Seed
|
|||||||
Console.WriteLine($"Seeded databases with episodes from {fileName}");
|
Console.WriteLine($"Seeded databases with episodes from {fileName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SeedQuotesAsync()
|
||||||
|
{
|
||||||
|
await _quotes.DeleteManyAsync(quotes => true);
|
||||||
|
|
||||||
|
const string fileName = "quotes.json";
|
||||||
|
var quotesFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
|
||||||
|
var quotesJson = await File.ReadAllTextAsync(quotesFilePath);
|
||||||
|
var quotes = JsonSerializer.Deserialize<List<Episode>>(quotesJson);
|
||||||
|
|
||||||
|
if (quotes != null)
|
||||||
|
{
|
||||||
|
await _episodes.InsertManyAsync(quotes);
|
||||||
|
Console.WriteLine($"Seeded databases with quotes from {fileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-3
@@ -8,6 +8,7 @@ using server.Persistence;
|
|||||||
using server.Persistence.Repositories;
|
using server.Persistence.Repositories;
|
||||||
using server.Persistence.Seed;
|
using server.Persistence.Seed;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using AspNetCoreRateLimit;
|
||||||
|
|
||||||
if (args.Length == 2 && args[0].ToLower() == "seed")
|
if (args.Length == 2 && args[0].ToLower() == "seed")
|
||||||
{
|
{
|
||||||
@@ -22,10 +23,27 @@ if (args.Length == 2 && args[0].ToLower() == "seed")
|
|||||||
{
|
{
|
||||||
await seeder.SeedEpisodesAsync();
|
await seeder.SeedEpisodesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args[1].ToLower() == "quotes")
|
||||||
|
{
|
||||||
|
await seeder.SeedQuotesAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
|
builder.Services.Configure<IpRateLimitOptions>(
|
||||||
|
builder.Configuration.GetSection("IpRateLimiting"));
|
||||||
|
|
||||||
|
builder.Services.Configure<IpRateLimitPolicies>(
|
||||||
|
builder.Configuration.GetSection("IpRateLimitPolicies"));
|
||||||
|
|
||||||
|
builder.Services.AddInMemoryRateLimiting();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||||
|
|
||||||
builder.Services.Configure<DatabaseSettings>(
|
builder.Services.Configure<DatabaseSettings>(
|
||||||
builder.Configuration.GetSection("MongoDBSettings"));
|
builder.Configuration.GetSection("MongoDBSettings"));
|
||||||
|
|
||||||
@@ -68,18 +86,27 @@ builder.Services.AddVersionedApiExplorer(config =>
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseSwagger();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseSwagger(options =>
|
||||||
|
{
|
||||||
|
options.RouteTemplate = "docs/{documentName}/docs.json";
|
||||||
|
});
|
||||||
|
|
||||||
app.UseSwaggerUI(options =>
|
app.UseSwaggerUI(options =>
|
||||||
{
|
{
|
||||||
var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
|
var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
|
||||||
|
|
||||||
foreach (var description in provider.ApiVersionDescriptions)
|
foreach (var description in provider.ApiVersionDescriptions)
|
||||||
{
|
{
|
||||||
var url = $"/swagger/{description.GroupName}/swagger.json";
|
var url = $"/docs/{description.GroupName}/docs.json";
|
||||||
var name = $"criminalmindsapi v{description.ApiVersion}";
|
var name = $"criminalmindsapi v{description.ApiVersion}";
|
||||||
|
|
||||||
|
options.RoutePrefix = "docs";
|
||||||
options.SwaggerEndpoint(url, name);
|
options.SwaggerEndpoint(url, name);
|
||||||
|
options.EnableTryItOutByDefault();
|
||||||
|
options.DisplayRequestDuration();
|
||||||
|
options.DocumentTitle = "criminalmindsapi";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,6 +116,8 @@ app.UseAuthorization();
|
|||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="docs\index.html" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="docs\index.html">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.16.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="2.16.0" />
|
<PackageReference Include="MongoDB.Driver" Version="2.16.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user