Merge pull request #7 from StevanFreeborn/master

Deploy master to production
This commit is contained in:
StevanFreeborn
2022-06-24 15:56:02 -05:00
committed by GitHub
18 changed files with 802 additions and 124 deletions
+35
View File
@@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/server/bin/Debug/net6.0/server.dll",
"args": [],
"cwd": "${workspaceFolder}/server",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
+4
View File
@@ -0,0 +1,4 @@
{
"appService.defaultWebAppToDeploy": "/subscriptions/d031910a-7ca1-44c2-9bb1-0bdb42cd9f43/microsoft.web/sites/subscriptions/d031910a-7ca1-44c2-9bb1-0bdb42cd9f43/resourceGroups/StevanFreeborn/providers/Microsoft.Web/sites/criminalmindsapi",
"appService.deploySubpath": "server"
}
+41
View File
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/server/server.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/server/server.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/server/server.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
+1 -1
View File
@@ -55,7 +55,7 @@ export default class App extends Component {
let url = '/api/seasons';
if (process.env.NODE_ENV === 'production') {
url = 'https://criminalminds-server.azurewebsites.net' + url;
url = 'https://criminalmindsapi.azurewebsites.net' + url;
}
const response = await fetch(url);
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using server.Models;
using server.Persistence.Repositories;
namespace server.Controllers.v1
{
[ApiController]
[ApiVersion("1.0")]
[Route("/api/characters")]
[Produces("application/json")]
public class CharactersController : ControllerBase
{
private readonly ICharacterRepository _characterRepository;
public CharactersController(ICharacterRepository characterRepository)
{
_characterRepository = characterRepository;
}
/// <summary>
/// Gets a collection of characters.
/// </summary>
/// <param name="filter">Filter parameters passed from query string.</param>
/// <response code="200">Returns the collection of characters requested.</response>
/// <response code="400">Not a valid request.</response>
/// <response code="500">Failed to get characters.</response>
/// <returns>Returns a collection of characters.</returns>
[MapToApiVersion("1.0")]
[HttpGet]
[ProducesResponseType(typeof(List<Character>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<Character>>> GetCharactersAsync([FromQuery] CharacterFilter? filter)
{
try
{
var character = await _characterRepository.GetCharactersAsync(filter);
return Ok(character);
}
catch (Exception e)
{
Console.WriteLine(e);
return Problem(detail: "Failed to get characters", statusCode: 500);
}
}
/// <summary>
/// Gets a character by its id.
/// </summary>
/// <param name="id">The id of the character being requested.</param>
/// <response code="200">Returns the character requested.</response>
/// <response code="400">Not a valid request.</response>
/// <response code="404">Character with the given id not found.</response>
/// <response code="500">Failed to get character.</response>
/// <returns>Returns a character.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id}")]
[ProducesResponseType(typeof(Character), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Character>> GetCharacterByIdAsync(string id)
{
if (!ObjectId.TryParse(id, out _))
{
ModelState.AddModelError(nameof(id), $"{id} is not a valid id");
return ValidationProblem();
}
try
{
var quote = await _characterRepository.GetCharacterByIdAsync(id);
return quote == null ?
Problem(detail: $"Could not find character {id}", statusCode: 404) :
Ok(quote);
}
catch (Exception e)
{
Console.WriteLine(e);
return Problem(detail: "Failed to get character", statusCode: 500);
}
}
}
}
+9 -1
View File
@@ -46,7 +46,15 @@ namespace server.Controllers.v1
}
}
// TODO: Add xml comments
/// <summary>
/// Gets a quote by its id.
/// </summary>
/// <param name="id">The id of the quote being requested.</param>
/// <response code="200">Returns the quote requested.</response>
/// <response code="400">Not a valid request.</response>
/// <response code="404">Quote with the given id not found.</response>
/// <response code="500">Failed to get quote.</response>
/// <returns>Returns a quote.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id}")]
[ProducesResponseType(typeof(Quote), StatusCodes.Status200OK)]
+12
View File
@@ -12,6 +12,12 @@ namespace server.Models
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
/// <summary>
/// The character's full name.
/// </summary>
[BsonElement("fullName")]
public string FullName => $"{FirstName} {LastName}";
/// <summary>
/// The character's first name.
/// </summary>
@@ -24,6 +30,12 @@ namespace server.Models
[BsonElement("lastName")]
public string? LastName { get; set; }
/// <summary>
/// The full name of the actor who potrayed the character.
/// </summary>
[BsonElement("actorFullName")]
public string ActorFullName => $"{ActorFirstName} {ActorLastName}";
/// <summary>
/// The first name of the actor who potrayed the character.
/// </summary>
+20
View File
@@ -0,0 +1,20 @@
namespace server.Models
{
public class CharacterFilter
{
/// <summary>
/// Used to filter characters by their name.
/// </summary>
public string? Name { get; set; } = null;
/// <summary>
/// Used to filter characters by the actor's name.
/// </summary>
public string? ActorName { get; set; } = null;
/// <summary>
/// Used to filter characters by a season.
/// </summary>
public int? Season { get; set; } = null;
}
}
@@ -0,0 +1,59 @@
using server.Models;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace server.Persistence.Repositories
{
public class CharacterRepository : ICharacterRepository
{
private readonly IDbContext _context;
public CharacterRepository(IDbContext context)
{
_context = context;
}
public async Task<List<Character>> GetCharactersAsync(CharacterFilter filter)
{
try
{
var query = _context.Characters.AsQueryable();
if (filter?.Name != null)
{
query = query.Where(character => character.FullName.ToLower().Contains(filter.Name.ToLower()));
}
if (filter?.ActorName != null)
{
query = query.Where(character => character.ActorFullName.ToLower().Contains(filter.ActorName.ToLower()));
}
if (filter?.Season != null)
{
query = query.Where(character => character.Seasons.Any(season => season == filter.Season));
}
return await query.ToListAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task<Character> GetCharacterByIdAsync(string id)
{
try
{
return await _context.Characters.Find(character => character.Id == id).SingleOrDefaultAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
@@ -0,0 +1,10 @@
using server.Models;
namespace server.Persistence.Repositories
{
public interface ICharacterRepository
{
Task<List<Character>> GetCharactersAsync(CharacterFilter filter);
Task<Character> GetCharacterByIdAsync(string id);
}
}
@@ -0,0 +1,351 @@
[
{
"FirstName": "Jason",
"LastName": "Gideon",
"ActorFirstName": "Mandy",
"ActorLastName": "Patinkin",
"Seasons": [
1,
2,
3,
10,
15
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "In Name and Blood",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/jason-gideon.png"
},
{
"FirstName": "Derek",
"LastName": "Morgan",
"ActorFirstName": "Shemar",
"ActorLastName": "Moore",
"Seasons": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "A Beautiful Disaster",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/derek-morgan.png"
},
{
"FirstName": "Aaron",
"LastName": "Hotchner",
"ActorFirstName": "Thomas",
"ActorLastName": "Gibson",
"Seasons": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "Sick Day",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/aaron-hotchner.png"
},
{
"FirstName": "Spencer",
"LastName": "Reid",
"ActorFirstName": "Matthew",
"ActorLastName": "Gubler",
"Seasons": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/spencer-reid.png"
},
{
"FirstName": "Max",
"LastName": "Ryan",
"ActorFirstName": "Geoff",
"ActorLastName": "Pierson",
"Seasons": [
1
],
"FirstEpisode": "Unfinished Business",
"LastEpisode": "Unfinished Business",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/max-ryan.png"
},
{
"FirstName": "Elle",
"LastName": "Greenaway",
"ActorFirstName": "Lola",
"ActorLastName": "Glaudini",
"Seasons": [
1,
2
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "The Boogeyman",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/elle-greenaway.png"
},
{
"FirstName": "Jennifer",
"LastName": "Jareau",
"ActorFirstName": "A.J.",
"ActorLastName": "Cook",
"Seasons": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
],
"FirstEpisode": "Complusion",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/jennifer-jareau.png"
},
{
"FirstName": "Emily",
"LastName": "Prentiss",
"ActorFirstName": "Paget",
"ActorLastName": "Brewster",
"Seasons": [
2,
3,
4,
5,
6,
7,
9,
11,
12,
13,
14,
15
],
"FirstEpisode": "The Last Word",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/emily-prentiss.png"
},
{
"FirstName": "David",
"LastName": "Rossi",
"ActorFirstName": "Joe",
"ActorLastName": "Mantegna",
"Seasons": [
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
],
"FirstEpisode": "About Face",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/david-rossi.png"
},
{
"FirstName": "Penelope",
"LastName": "Garcia",
"ActorFirstName": "Kirsten",
"ActorLastName": "Vangsness",
"Seasons": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
],
"FirstEpisode": "Extreme Aggressor",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/penelope-garcia.png"
},
{
"FirstName": "Ian",
"LastName": "Doyle",
"ActorFirstName": "Timothy",
"ActorLastName": "Murphy",
"Seasons": [
6,
7
],
"FirstEpisode": "The Thirteenth Step",
"LastEpisode": "It Takes a Village",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/ian-doyle.png"
},
{
"FirstName": "Ashley",
"LastName": "Seaver",
"ActorFirstName": "Rachel",
"ActorLastName": "Nichols",
"Seasons": [
6
],
"FirstEpisode": "What Happens at Home",
"LastEpisode": "Supply & Demand",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/ashley-seaver.png"
},
{
"FirstName": "Alex",
"LastName": "Blake",
"ActorFirstName": "Jeanne",
"ActorLastName": "Tripplehorn",
"Seasons": [
8,
9
],
"FirstEpisode": "The Silencer",
"LastEpisode": "Demons",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/alex-blake.png"
},
{
"FirstName": "Kate",
"LastName": "Callahan",
"ActorFirstName": "Jennifer",
"ActorLastName": "Hewitt",
"Seasons": [
10
],
"FirstEpisode": "X",
"LastEpisode": "The Hunt",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/kate-callahan.png"
},
{
"FirstName": "Jack",
"LastName": "Garrett",
"ActorFirstName": "Gary",
"ActorLastName": "Sinise",
"Seasons": [
10
],
"FirstEpisode": "Beyond Borders",
"LastEpisode": "Beyond Borders",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/jack-garrett.png"
},
{
"FirstName": "Tara",
"LastName": "Lewis",
"ActorFirstName": "Aisha",
"ActorLastName": "Tyler",
"Seasons": [
11,
12,
13,
14,
15
],
"FirstEpisode": "The Job",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/tara-lewis.png"
},
{
"FirstName": "Luke",
"LastName": "Alvez",
"ActorFirstName": "Adam",
"ActorLastName": "Rodriguez",
"Seasons": [
12,
13,
14,
15
],
"FirstEpisode": "The Crimson King",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/luke-alvez.png"
},
{
"FirstName": "Stephen",
"LastName": "Walker",
"ActorFirstName": "Damon",
"ActorLastName": "Gupton",
"Seasons": [
12,
13
],
"FirstEpisode": "Scarecrow",
"LastEpisode": "Wheels Up",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/stephen-walker.png"
},
{
"FirstName": "Matthew",
"LastName": "Simmons",
"ActorFirstName": "Daniel",
"ActorLastName": "Henney",
"Seasons": [
13,
14,
15
],
"FirstEpisode": "Beyond Borders",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/matthew-simmons.png"
},
{
"FirstName": "Krystall",
"LastName": "Richards",
"ActorFirstName": "Gail",
"ActorLastName": "O'Grady",
"Seasons": [
8,
13,
14,
15
],
"FirstEpisode": "The Dance of Love",
"LastEpisode": "And in the End",
"Image": "https://criminalmindsapi.stevanfreeborn.com/characters/krystall-richards.png"
}
]
+23 -4
View File
@@ -9,6 +9,7 @@ namespace server.Persistence.Seed
private readonly IMongoCollection<Season> _seasons;
private readonly IMongoCollection<Episode> _episodes;
private readonly IMongoCollection<Quote> _quotes;
private readonly IMongoCollection<Character> _characters;
public Seeder()
{
@@ -23,6 +24,7 @@ namespace server.Persistence.Seed
_seasons = database.GetCollection<Season>(config.GetSection("MongoDBSettings:SeasonsCollection").Value);
_episodes = database.GetCollection<Episode>(config.GetSection("MongoDBSettings:EpisodesCollection").Value);
_quotes = database.GetCollection<Quote>(config.GetSection("MongoDBSettings:QuotesCollection").Value);
_characters = database.GetCollection<Character>(config.GetSection("MongoDBSettings:CharactersCollection").Value);
}
public async Task SeedSeasonsAsync()
@@ -37,7 +39,7 @@ namespace server.Persistence.Seed
if(seasons != null)
{
await _seasons.InsertManyAsync(seasons);
Console.WriteLine($"Seeded databases with seasons from {fileName}");
Console.WriteLine($"Seeded databases with {nameof(seasons)} from {fileName}");
}
}
@@ -53,13 +55,13 @@ namespace server.Persistence.Seed
if (episodes != null)
{
await _episodes.InsertManyAsync(episodes);
Console.WriteLine($"Seeded databases with episodes from {fileName}");
Console.WriteLine($"Seeded databases with {nameof(episodes)} from {fileName}");
}
}
public async Task SeedQuotesAsync()
{
await _quotes.DeleteManyAsync(quotes => true);
await _quotes.DeleteManyAsync(quote => true);
const string fileName = "quotes.json";
var quotesFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
@@ -69,7 +71,24 @@ namespace server.Persistence.Seed
if (quotes != null)
{
await _quotes.InsertManyAsync(quotes);
Console.WriteLine($"Seeded databases with quotes from {fileName}");
Console.WriteLine($"Seeded databases with {nameof(quotes)} from {fileName}");
}
}
public async Task SeedCharactersAsync()
{
await _characters.DeleteManyAsync(character => true);
const string fileName = "characters.json";
var charactersFilePath = Path.Combine(AppContext.BaseDirectory, $"Persistence/Seed/Data/{fileName}");
var charactersJson = await File.ReadAllTextAsync(charactersFilePath);
var characters = JsonSerializer.Deserialize<List<Character>>(charactersJson);
if (characters != null)
{
await _characters.InsertManyAsync(characters);
Console.WriteLine($"Seeded databases with {nameof(characters)} from {fileName}");
}
}
+18 -115
View File
@@ -1,125 +1,28 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Options;
using server.Filters;
using server.Options;
using server.Persistence;
using server.Persistence.Repositories;
using server.Persistence.Seed;
using System.Reflection;
using AspNetCoreRateLimit;
using server.Setup;
if (args.Length == 2 && args[0].ToLower() == "seed")
if (args.Length > 0 && args[0].ToLower() == "seed")
{
var seeder = new Seeder();
if (args[1].ToLower() == "seasons")
{
await seeder.SeedSeasonsAsync();
}
if (args.Contains("seasons")) await seeder.SeedSeasonsAsync();
if (args[1].ToLower() == "episodes")
{
await seeder.SeedEpisodesAsync();
}
if (args.Contains("episodes")) await seeder.SeedEpisodesAsync();
if (args[1].ToLower() == "quotes")
{
await seeder.SeedQuotesAsync();
}
if (args.Contains("quotes")) await seeder.SeedQuotesAsync();
if (args.Contains("characters")) await seeder.SeedCharactersAsync();
}
else
{
var app = WebApplication
.CreateBuilder(args)
.SetupDb()
.SetupMvC()
.SetupSwagger()
.Build();
app.SetupMiddleware().Run();
}
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.Configuration.GetSection("MongoDBSettings"));
builder.Services.AddSingleton<IDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
builder.Services.AddSingleton<IDbContext, DbContext>();
builder.Services.AddScoped<ISeasonRepository, SeasonRepository>();
builder.Services.AddScoped<IEpisodeRepository, EpisodeRepository>();
builder.Services.AddScoped<IQuoteRepository, QuoteRepository>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
options.OperationFilter<ApiVersionOperationFilter>();
});
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
builder.Services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
config.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
builder.Services.AddVersionedApiExplorer(config =>
{
config.GroupNameFormat = "'v'VVV";
});
var app = builder.Build();
app.UseStaticFiles();
app.UseSwagger(options =>
{
options.RouteTemplate = "/{documentName}/docs.json";
});
app.UseSwaggerUI(options =>
{
var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
var url = $"/{description.GroupName}/docs.json";
var name = $"criminalmindsapi v{description.ApiVersion}";
options.RoutePrefix = String.Empty;
options.SwaggerEndpoint(url, name);
options.EnableTryItOutByDefault();
options.DisplayRequestDuration();
options.DocumentTitle = "criminalmindsapi";
}
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseIpRateLimiting();
app.Run();
@@ -21,7 +21,7 @@
},
"resourceName": {
"type": "string",
"defaultValue": "criminalminds-server",
"defaultValue": "criminalmindsapi",
"metadata": {
"description": "Name of the main resource to be created by this template."
}
+43
View File
@@ -0,0 +1,43 @@
using AspNetCoreRateLimit;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
namespace server.Setup
{
public static class Middleware
{
public static WebApplication SetupMiddleware(this WebApplication app)
{
app.UseSwagger(options =>
{
options.RouteTemplate = "/{documentName}/docs.json";
});
app.UseSwaggerUI(options =>
{
var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
var url = $"/{description.GroupName}/docs.json";
var name = $"criminalmindsapi v{description.ApiVersion}";
options.RoutePrefix = String.Empty;
options.SwaggerEndpoint(url, name);
options.EnableTryItOutByDefault();
options.DisplayRequestDuration();
options.DocumentTitle = "criminalmindsapi";
}
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseIpRateLimiting();
return app;
}
}
}
+86
View File
@@ -0,0 +1,86 @@
using AspNetCoreRateLimit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Options;
using server.Persistence;
using server.Persistence.Repositories;
using server.SwaggerOptions;
using server.SwaggerOptions.Filters;
using System.Reflection;
namespace server.Setup
{
public static class Services
{
public static WebApplicationBuilder SetupDb(this WebApplicationBuilder builder)
{
builder.Services.Configure<DatabaseSettings>(
builder.Configuration.GetSection("MongoDBSettings"));
builder.Services.AddSingleton<IDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
builder.Services.AddSingleton<IDbContext, DbContext>();
builder.Services.AddScoped<ISeasonRepository, SeasonRepository>();
builder.Services.AddScoped<IEpisodeRepository, EpisodeRepository>();
builder.Services.AddScoped<IQuoteRepository, QuoteRepository>();
builder.Services.AddScoped<ICharacterRepository, CharacterRepository>();
return builder;
}
public static WebApplicationBuilder SetupMvC(this WebApplicationBuilder builder)
{
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.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
config.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
builder.Services.AddVersionedApiExplorer(config =>
{
config.GroupNameFormat = "'v'VVV";
});
return builder;
}
public static WebApplicationBuilder SetupSwagger(this WebApplicationBuilder builder)
{
builder.Services.AddSwaggerGen(options =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
options.OperationFilter<ApiVersionOperationFilter>();
});
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
return builder;
}
}
}
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace server.Options
namespace server.SwaggerOptions
{
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
@@ -2,7 +2,7 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace server.Filters
namespace server.SwaggerOptions.Filters
{
public class ApiVersionOperationFilter : IOperationFilter
{