diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..4e6a45b
--- /dev/null
+++ b/.vscode/launch.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4db21f2
--- /dev/null
+++ b/.vscode/settings.json
@@ -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"
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..5239770
--- /dev/null
+++ b/.vscode/tasks.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/src/App.js b/client/src/App.js
index 4ec4478..ccfadae 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -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);
diff --git a/server/Controllers/v1/CharactersController.cs b/server/Controllers/v1/CharactersController.cs
new file mode 100644
index 0000000..9e1c3e6
--- /dev/null
+++ b/server/Controllers/v1/CharactersController.cs
@@ -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;
+ }
+
+ ///
+ /// Gets a collection of characters.
+ ///
+ /// Filter parameters passed from query string.
+ /// Returns the collection of characters requested.
+ /// Not a valid request.
+ /// Failed to get characters.
+ /// Returns a collection of characters.
+ [MapToApiVersion("1.0")]
+ [HttpGet]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
+ public async Task>> 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);
+ }
+ }
+
+ ///
+ /// Gets a character by its id.
+ ///
+ /// The id of the character being requested.
+ /// Returns the character requested.
+ /// Not a valid request.
+ /// Character with the given id not found.
+ /// Failed to get character.
+ /// Returns a character.
+ [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> 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);
+ }
+ }
+ }
+}
diff --git a/server/Controllers/v1/QuotesController.cs b/server/Controllers/v1/QuotesController.cs
index 08de272..e8f838c 100644
--- a/server/Controllers/v1/QuotesController.cs
+++ b/server/Controllers/v1/QuotesController.cs
@@ -46,7 +46,15 @@ namespace server.Controllers.v1
}
}
- // TODO: Add xml comments
+ ///
+ /// Gets a quote by its id.
+ ///
+ /// The id of the quote being requested.
+ /// Returns the quote requested.
+ /// Not a valid request.
+ /// Quote with the given id not found.
+ /// Failed to get quote.
+ /// Returns a quote.
[MapToApiVersion("1.0")]
[HttpGet("{id}")]
[ProducesResponseType(typeof(Quote), StatusCodes.Status200OK)]
diff --git a/server/Models/Character.cs b/server/Models/Character.cs
index 46fada7..15e18ab 100644
--- a/server/Models/Character.cs
+++ b/server/Models/Character.cs
@@ -12,6 +12,12 @@ namespace server.Models
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
+ ///
+ /// The character's full name.
+ ///
+ [BsonElement("fullName")]
+ public string FullName => $"{FirstName} {LastName}";
+
///
/// The character's first name.
///
@@ -24,6 +30,12 @@ namespace server.Models
[BsonElement("lastName")]
public string? LastName { get; set; }
+ ///
+ /// The full name of the actor who potrayed the character.
+ ///
+ [BsonElement("actorFullName")]
+ public string ActorFullName => $"{ActorFirstName} {ActorLastName}";
+
///
/// The first name of the actor who potrayed the character.
///
diff --git a/server/Models/CharacterFilter.cs b/server/Models/CharacterFilter.cs
new file mode 100644
index 0000000..35b0ef0
--- /dev/null
+++ b/server/Models/CharacterFilter.cs
@@ -0,0 +1,20 @@
+namespace server.Models
+{
+ public class CharacterFilter
+ {
+ ///
+ /// Used to filter characters by their name.
+ ///
+ public string? Name { get; set; } = null;
+
+ ///
+ /// Used to filter characters by the actor's name.
+ ///
+ public string? ActorName { get; set; } = null;
+
+ ///
+ /// Used to filter characters by a season.
+ ///
+ public int? Season { get; set; } = null;
+ }
+}
diff --git a/server/Persistence/Repositories/CharacterRepository.cs b/server/Persistence/Repositories/CharacterRepository.cs
new file mode 100644
index 0000000..5838d55
--- /dev/null
+++ b/server/Persistence/Repositories/CharacterRepository.cs
@@ -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> 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 GetCharacterByIdAsync(string id)
+ {
+ try
+ {
+ return await _context.Characters.Find(character => character.Id == id).SingleOrDefaultAsync();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+ }
+}
diff --git a/server/Persistence/Repositories/ICharacterRepository.cs b/server/Persistence/Repositories/ICharacterRepository.cs
new file mode 100644
index 0000000..b9b74a5
--- /dev/null
+++ b/server/Persistence/Repositories/ICharacterRepository.cs
@@ -0,0 +1,10 @@
+using server.Models;
+
+namespace server.Persistence.Repositories
+{
+ public interface ICharacterRepository
+ {
+ Task> GetCharactersAsync(CharacterFilter filter);
+ Task GetCharacterByIdAsync(string id);
+ }
+}
diff --git a/server/Persistence/Seed/Data/characters.json b/server/Persistence/Seed/Data/characters.json
new file mode 100644
index 0000000..c1c10b1
--- /dev/null
+++ b/server/Persistence/Seed/Data/characters.json
@@ -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"
+ }
+]
\ No newline at end of file
diff --git a/server/Persistence/Seed/Seeder.cs b/server/Persistence/Seed/Seeder.cs
index 521bb7a..d48a210 100644
--- a/server/Persistence/Seed/Seeder.cs
+++ b/server/Persistence/Seed/Seeder.cs
@@ -9,6 +9,7 @@ namespace server.Persistence.Seed
private readonly IMongoCollection _seasons;
private readonly IMongoCollection _episodes;
private readonly IMongoCollection _quotes;
+ private readonly IMongoCollection _characters;
public Seeder()
{
@@ -23,6 +24,7 @@ namespace server.Persistence.Seed
_seasons = database.GetCollection(config.GetSection("MongoDBSettings:SeasonsCollection").Value);
_episodes = database.GetCollection(config.GetSection("MongoDBSettings:EpisodesCollection").Value);
_quotes = database.GetCollection(config.GetSection("MongoDBSettings:QuotesCollection").Value);
+ _characters = database.GetCollection(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>(charactersJson);
+
+ if (characters != null)
+ {
+ await _characters.InsertManyAsync(characters);
+ Console.WriteLine($"Seeded databases with {nameof(characters)} from {fileName}");
}
}
diff --git a/server/Program.cs b/server/Program.cs
index baa6bb0..22027c5 100644
--- a/server/Program.cs
+++ b/server/Program.cs
@@ -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(
- builder.Configuration.GetSection("IpRateLimiting"));
-
-builder.Services.Configure(
- builder.Configuration.GetSection("IpRateLimitPolicies"));
-
-builder.Services.AddInMemoryRateLimiting();
-
-builder.Services.AddSingleton();
-
-builder.Services.Configure(
- builder.Configuration.GetSection("MongoDBSettings"));
-
-builder.Services.AddSingleton(sp =>
- sp.GetRequiredService>().Value);
-
-builder.Services.AddSingleton();
-
-builder.Services.AddScoped();
-
-builder.Services.AddScoped();
-
-builder.Services.AddScoped();
-
-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();
-});
-
-builder.Services.ConfigureOptions();
-
-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();
-
- 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();
-
diff --git a/server/Properties/ServiceDependencies/criminalminds-server - Web Deploy/profile.arm.json b/server/Properties/ServiceDependencies/criminalmindsapi - Web Deploy/profile.arm.json
similarity index 98%
rename from server/Properties/ServiceDependencies/criminalminds-server - Web Deploy/profile.arm.json
rename to server/Properties/ServiceDependencies/criminalmindsapi - Web Deploy/profile.arm.json
index 4b91ec2..9d32f2f 100644
--- a/server/Properties/ServiceDependencies/criminalminds-server - Web Deploy/profile.arm.json
+++ b/server/Properties/ServiceDependencies/criminalmindsapi - Web Deploy/profile.arm.json
@@ -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."
}
diff --git a/server/Setup/Middleware.cs b/server/Setup/Middleware.cs
new file mode 100644
index 0000000..7799abf
--- /dev/null
+++ b/server/Setup/Middleware.cs
@@ -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();
+
+ 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;
+ }
+ }
+}
diff --git a/server/Setup/Services.cs b/server/Setup/Services.cs
new file mode 100644
index 0000000..1ba5bab
--- /dev/null
+++ b/server/Setup/Services.cs
@@ -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(
+ builder.Configuration.GetSection("MongoDBSettings"));
+
+ builder.Services.AddSingleton(sp =>
+ sp.GetRequiredService>().Value);
+
+ builder.Services.AddSingleton();
+
+ builder.Services.AddScoped();
+
+ builder.Services.AddScoped();
+
+ builder.Services.AddScoped();
+
+ builder.Services.AddScoped();
+
+ return builder;
+ }
+
+ public static WebApplicationBuilder SetupMvC(this WebApplicationBuilder builder)
+ {
+ builder.Services.AddMemoryCache();
+
+ builder.Services.Configure(
+ builder.Configuration.GetSection("IpRateLimiting"));
+
+ builder.Services.Configure(
+ builder.Configuration.GetSection("IpRateLimitPolicies"));
+
+ builder.Services.AddInMemoryRateLimiting();
+
+ builder.Services.AddSingleton();
+
+ 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();
+ });
+
+ builder.Services.ConfigureOptions();
+
+ return builder;
+ }
+ }
+}
diff --git a/server/Options/ConfigureSwaggerOptions.cs b/server/SwaggerOptions/ConfigureSwaggerOptions.cs
similarity index 97%
rename from server/Options/ConfigureSwaggerOptions.cs
rename to server/SwaggerOptions/ConfigureSwaggerOptions.cs
index 8d2a8aa..a919623 100644
--- a/server/Options/ConfigureSwaggerOptions.cs
+++ b/server/SwaggerOptions/ConfigureSwaggerOptions.cs
@@ -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
{
diff --git a/server/Filters/ApiVersionOperationFilter.cs b/server/SwaggerOptions/Filters/ApiVersionOperationFilter.cs
similarity index 94%
rename from server/Filters/ApiVersionOperationFilter.cs
rename to server/SwaggerOptions/Filters/ApiVersionOperationFilter.cs
index b49cd1c..4445471 100644
--- a/server/Filters/ApiVersionOperationFilter.cs
+++ b/server/SwaggerOptions/Filters/ApiVersionOperationFilter.cs
@@ -2,7 +2,7 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
-namespace server.Filters
+namespace server.SwaggerOptions.Filters
{
public class ApiVersionOperationFilter : IOperationFilter
{