From f9e7114b3b827ccd67962da27c9deb1df8bc3c0f Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Fri, 18 Oct 2019 18:44:10 +0200 Subject: [PATCH 1/6] Fix issues raised by SonarCloud --- dotnet/KeepTrack.sln | 18 +++++++++++------ dotnet/README.md | 20 +++++++++++++++++++ .../Entities/Car.cs | 3 +-- .../Repositories/CarHistoryRepository.cs | 5 +++-- .../Repositories/CarRepository.cs | 6 +++--- dotnet/src/ConsoleApp/Program.cs | 4 ++-- 6 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 dotnet/README.md diff --git a/dotnet/KeepTrack.sln b/dotnet/KeepTrack.sln index e63bdfcf..1908d68b 100644 --- a/dotnet/KeepTrack.sln +++ b/dotnet/KeepTrack.sln @@ -15,12 +15,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Car", "Car", "{A52EF787-A7D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Movie", "Movie", "{A6A8275A-6360-43E7-AB5A-D8A1E96BED06}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "src\ConsoleApp\ConsoleApp.csproj", "{09DBB876-546F-40EF-B1D8-D0FDAE02E80D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "src\ConsoleApp\ConsoleApp.csproj", "{3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarComponent.Domain", "src\CarComponent.Domain\CarComponent.Domain.csproj", "{A2193B19-8191-4A28-9E64-23D9083A0531}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarComponent.Infrastructure.MongoDb", "src\CarComponent.Infrastructure.MongoDb\CarComponent.Infrastructure.MongoDb.csproj", "{F7250DAA-718B-44FB-990B-AD7DFC488AA7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{73A074B8-D41E-4CCD-AC4B-95DB716EC6C6}" + ProjectSection(SolutionItems) = preProject + CodeCoverage.runsettings = CodeCoverage.runsettings + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,10 +37,10 @@ Global {6A10979B-FBAE-488A-B5BC-0E45716DFE88}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A10979B-FBAE-488A-B5BC-0E45716DFE88}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A10979B-FBAE-488A-B5BC-0E45716DFE88}.Release|Any CPU.Build.0 = Release|Any CPU - {09DBB876-546F-40EF-B1D8-D0FDAE02E80D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09DBB876-546F-40EF-B1D8-D0FDAE02E80D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09DBB876-546F-40EF-B1D8-D0FDAE02E80D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09DBB876-546F-40EF-B1D8-D0FDAE02E80D}.Release|Any CPU.Build.0 = Release|Any CPU + {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57}.Release|Any CPU.Build.0 = Release|Any CPU {A2193B19-8191-4A28-9E64-23D9083A0531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A2193B19-8191-4A28-9E64-23D9083A0531}.Debug|Any CPU.Build.0 = Debug|Any CPU {A2193B19-8191-4A28-9E64-23D9083A0531}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -51,7 +57,7 @@ Global {6A10979B-FBAE-488A-B5BC-0E45716DFE88} = {215584EC-8DFC-4DAD-B3A0-D419CEC24260} {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} = {59B2BDBC-F7DE-47F8-880E-248202630B1A} {A6A8275A-6360-43E7-AB5A-D8A1E96BED06} = {59B2BDBC-F7DE-47F8-880E-248202630B1A} - {09DBB876-546F-40EF-B1D8-D0FDAE02E80D} = {61C528CD-816E-4FC5-ADFA-C5C25A3C4052} + {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57} = {61C528CD-816E-4FC5-ADFA-C5C25A3C4052} {A2193B19-8191-4A28-9E64-23D9083A0531} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} {F7250DAA-718B-44FB-990B-AD7DFC488AA7} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} EndGlobalSection diff --git a/dotnet/README.md b/dotnet/README.md new file mode 100644 index 00000000..3e9902c7 --- /dev/null +++ b/dotnet/README.md @@ -0,0 +1,20 @@ +# Keep track .NET solution + +[![Build Status](https://dev.azure.com/devprofr/open-source/_apis/build/status/keeptrack-CI?branchName=master)](https://dev.azure.com/devprofr/open-source/_build/latest?definitionId=18&branchName=master) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=devpro.keep-track&metric=alert_status)](https://sonarcloud.io/dashboard?id=devpro.keep-track) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=devpro.keep-track&metric=coverage)](https://sonarcloud.io/dashboard?id=devpro.keep-track) + +## Dependencies + +- .NET Core 3.0 SDK +- MongoDB 4.2 + +## Configuration + +- Value for key `KeepTrack_MongoDbConnectionString`: .NET connection string to access MongoDB cluster + +## Local run + +- Clone the solution: `git clone ...` +- Build the solution: `dotnet build` + diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs index 2818c947..b0c4e622 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs @@ -1,5 +1,4 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Attributes; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities { diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs index 4077c6b6..ac14c0e2 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using KeepTrack.CarComponent.Domain; @@ -24,7 +25,7 @@ public async Task FindOneAsync(string id) { if (!ObjectId.TryParse(id, out var objectId)) { - throw new System.Exception($"Cannot find the car history. \"{id}\" is not a valid id."); + throw new ArgumentNullException(nameof(id), $"Cannot find the car history. \"{id}\" is not a valid id."); } var collection = GetCollection(); diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarRepository.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarRepository.cs index 98c4591a..c085bed0 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarRepository.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarRepository.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using AutoMapper; using KeepTrack.CarComponent.Domain; using KeepTrack.CarComponent.Infrastructure.MongoDb.Entities; using Microsoft.Extensions.Logging; -using MongoDB.Bson; using MongoDB.Driver; using Withywoods.Dal.MongoDb; using Withywoods.Dal.MongoDb.Repositories; @@ -23,7 +23,7 @@ public async Task FindOneAsync(string id) { if (string.IsNullOrEmpty(id)) { - throw new System.Exception($"Cannot find a car. \"{id}\" is not a valid id."); + throw new ArgumentNullException(nameof(id), $"Cannot find a car. \"{id}\" is not a valid id."); } var collection = GetCollection(); diff --git a/dotnet/src/ConsoleApp/Program.cs b/dotnet/src/ConsoleApp/Program.cs index e11c9179..7b1c6765 100644 --- a/dotnet/src/ConsoleApp/Program.cs +++ b/dotnet/src/ConsoleApp/Program.cs @@ -41,7 +41,7 @@ private async static Task Main(string[] args) await Parser.Default.ParseArguments(args) .MapResult( (CommandLineOptions opts) => RunOptionsAndReturnExitCode(opts), - errs => Task.FromResult(HandleParseError(errs)) + errs => Task.FromResult(HandleParseError()) ); } @@ -76,7 +76,7 @@ private async static Task RunOptionsAndReturnExitCode(CommandLineOptions op } } - private static int HandleParseError(IEnumerable errs) + private static int HandleParseError() { return -2; } From b37fa306fdcef5c22388a2f3061547841ee272cc Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Wed, 23 Oct 2019 09:57:53 +0200 Subject: [PATCH 2/6] Create Api with CarHistory CRUD --- dotnet/.gitignore | 1 + dotnet/KeepTrack.sln | 14 ++ dotnet/README.md | 11 +- dotnet/src/Api/Api.csproj | 32 +++++ dotnet/src/Api/Api.csproj.user | 17 +++ dotnet/src/Api/AppConfiguration.cs | 75 ++++++++++ .../Api/Controllers/CarHistoryController.cs | 132 ++++++++++++++++++ dotnet/src/Api/Dto/CarHistoryDto.cs | 90 ++++++++++++ .../Api/MappingProfiles/CarMappingProfile.cs | 28 ++++ dotnet/src/Api/Program.cs | 34 +++++ dotnet/src/Api/Properties/launchSettings.json | 30 ++++ dotnet/src/Api/Startup.cs | 121 ++++++++++++++++ dotnet/src/Api/appsettings.json | 15 ++ .../CarComponent.Domain/CarHistoryModel.cs | 31 ++++ dotnet/src/CarComponent.Domain/CarModel.cs | 3 + .../ICarHistoryRepository.cs | 7 + .../Entities/Car.cs | 4 + .../Entities/CarHistory.cs | 26 +++- .../Entities/CarHistoryFuel.cs | 29 ++++ .../Entities/CarHistoryLocation.cs | 11 ++ .../Entities/CarHistoryStation.cs | 11 ++ .../MappingProfiles/CarMappingProfile.cs | 26 +++- .../Repositories/CarHistoryRepository.cs | 40 +++++- dotnet/src/ConsoleApp/Program.cs | 25 ++-- .../CarComponent.Domain.UnitTests.csproj | 20 +++ 25 files changed, 810 insertions(+), 23 deletions(-) create mode 100644 dotnet/src/Api/Api.csproj create mode 100644 dotnet/src/Api/Api.csproj.user create mode 100644 dotnet/src/Api/AppConfiguration.cs create mode 100644 dotnet/src/Api/Controllers/CarHistoryController.cs create mode 100644 dotnet/src/Api/Dto/CarHistoryDto.cs create mode 100644 dotnet/src/Api/MappingProfiles/CarMappingProfile.cs create mode 100644 dotnet/src/Api/Program.cs create mode 100644 dotnet/src/Api/Properties/launchSettings.json create mode 100644 dotnet/src/Api/Startup.cs create mode 100644 dotnet/src/Api/appsettings.json create mode 100644 dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryFuel.cs create mode 100644 dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs create mode 100644 dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs create mode 100644 dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj diff --git a/dotnet/.gitignore b/dotnet/.gitignore index 69824299..c2f28145 100644 --- a/dotnet/.gitignore +++ b/dotnet/.gitignore @@ -2,3 +2,4 @@ Debug/ Release/ obj/ +appsettings.Development.json diff --git a/dotnet/KeepTrack.sln b/dotnet/KeepTrack.sln index 1908d68b..f46442a7 100644 --- a/dotnet/KeepTrack.sln +++ b/dotnet/KeepTrack.sln @@ -27,6 +27,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "src\Api\Api.csproj", "{5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarComponent.Domain.UnitTests", "test\CarComponent.Domain.UnitTests\CarComponent.Domain.UnitTests.csproj", "{2079D00A-8489-4A43-BC2E-0B7CFB772CE7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,6 +53,14 @@ Global {F7250DAA-718B-44FB-990B-AD7DFC488AA7}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7250DAA-718B-44FB-990B-AD7DFC488AA7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7250DAA-718B-44FB-990B-AD7DFC488AA7}.Release|Any CPU.Build.0 = Release|Any CPU + {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560}.Release|Any CPU.Build.0 = Release|Any CPU + {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -60,6 +72,8 @@ Global {3C4E6452-A0A7-4860-83F1-4AF7A0CF1E57} = {61C528CD-816E-4FC5-ADFA-C5C25A3C4052} {A2193B19-8191-4A28-9E64-23D9083A0531} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} {F7250DAA-718B-44FB-990B-AD7DFC488AA7} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} + {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560} = {61C528CD-816E-4FC5-ADFA-C5C25A3C4052} + {2079D00A-8489-4A43-BC2E-0B7CFB772CE7} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {876333E7-72D4-4F43-B6F9-9432CA2B7425} diff --git a/dotnet/README.md b/dotnet/README.md index 3e9902c7..f159eca4 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,4 +1,4 @@ -# Keep track .NET solution +# Keep track .NET solution [![Build Status](https://dev.azure.com/devprofr/open-source/_apis/build/status/keeptrack-CI?branchName=master)](https://dev.azure.com/devprofr/open-source/_build/latest?definitionId=18&branchName=master) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=devpro.keep-track&metric=alert_status)](https://sonarcloud.io/dashboard?id=devpro.keep-track) @@ -6,15 +6,16 @@ ## Dependencies -- .NET Core 3.0 SDK -- MongoDB 4.2 +- SDK: .NET Core 3.0 +- DB: MongoDB 4.2 ## Configuration -- Value for key `KeepTrack_MongoDbConnectionString`: .NET connection string to access MongoDB cluster +- Value for key `KeepTrack_MongoDbConnectionString`: .NET connection string to access MongoDB cluster, ideally set as an environment variable. ## Local run - Clone the solution: `git clone ...` - Build the solution: `dotnet build` - +- Run the console: `dotnet dotnet src\ConsoleApp\bin\Debug\netcoreapp3.0\KeepTrack.ConsoleApp.dll ...` +- Run the web api: `dotnet run --project src\Api` diff --git a/dotnet/src/Api/Api.csproj b/dotnet/src/Api/Api.csproj new file mode 100644 index 00000000..e0d7e086 --- /dev/null +++ b/dotnet/src/Api/Api.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.0 + KeepTrack.Api + KeepTrack.Api + {651432D5-8481-4807-A422-552EEB4DE8C2} + true + + + + full + true + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/Api/Api.csproj.user b/dotnet/src/Api/Api.csproj.user new file mode 100644 index 00000000..d012339b --- /dev/null +++ b/dotnet/src/Api/Api.csproj.user @@ -0,0 +1,17 @@ + + + + ProjectDebugger + + + Api + ApiControllerWithActionsScaffolder + root/Controller + 600 + True + False + True + + False + + \ No newline at end of file diff --git a/dotnet/src/Api/AppConfiguration.cs b/dotnet/src/Api/AppConfiguration.cs new file mode 100644 index 00000000..8fb69239 --- /dev/null +++ b/dotnet/src/Api/AppConfiguration.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.OpenApi.Models; +using Withywoods.Configuration; +using Withywoods.Dal.MongoDb; +using Withywoods.Dal.MongoDb.Serialization; + +namespace KeepTrack.Api +{ + /// + /// Web application configuration. + /// This class implements the interface from the libraries that are used in the application. + /// + public class AppConfiguration : IMongoDbConfiguration + { + #region Constructor & private fields + + /// + /// Create a new instance of + /// + /// + public AppConfiguration(IConfiguration configurationRoot) + { + ConfigurationRoot = configurationRoot; + } + + /// + /// Configuration root. + /// + public IConfiguration ConfigurationRoot { get; set; } + + #endregion + + #region IMongoDbConfiguration properties + + /// + /// MongoDB connection string => secret! + /// This is really a sensitive information so better defined as an environment variable. + /// + public string ConnectionString => ConfigurationRoot.TryGetSection("KeepTrack_MongoDbConnectionString").Value; + + /// + /// MongoDB collection name. + /// + public string DatabaseName => ConfigurationRoot.TryGetSection("Infrastructure:MongoDB:DatabaseName").Value; + + /// + /// MongoDB serialization conventions. + /// + public List SerializationConventions => + new List + { + ConventionValues.CamelCaseElementName, + ConventionValues.EnumAsString, + ConventionValues.IgnoreExtraElements, + ConventionValues.IgnoreNullValues + }; + + #endregion + + #region General properties + + /// + /// Open API information. + /// + public OpenApiInfo OpenApiInfo => + new OpenApiInfo + { + Title = "Keep Track API", + Version = "1.0" + }; + + #endregion + } +} diff --git a/dotnet/src/Api/Controllers/CarHistoryController.cs b/dotnet/src/Api/Controllers/CarHistoryController.cs new file mode 100644 index 00000000..9e311837 --- /dev/null +++ b/dotnet/src/Api/Controllers/CarHistoryController.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using KeepTrack.Api.Dto; +using KeepTrack.CarComponent.Domain; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers +{ + /// + /// Car history controller. + /// + [ApiController] + // [Authorize()] + [Route("api/car-history")] + public class CarHistoryController : ControllerBase + { + private readonly IMapper _mapper; + private readonly ICarHistoryRepository _carHistoryRepository; + + /// + /// Creates a new instance of . + /// + /// + /// + public CarHistoryController(IMapper mapper, ICarHistoryRepository carHistoryRepository) + { + _mapper = mapper; + _carHistoryRepository = carHistoryRepository; + } + + /// + /// Gets all the history for a given car. + /// + /// Car ID + /// + // GET: api/CarHistory + [HttpGet] + [ProducesResponseType(200, Type = typeof(List))] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Get(string carId) + { + if (string.IsNullOrEmpty(carId)) + { + return BadRequest(); + } + + var models = await _carHistoryRepository.FindAllAsync(carId); + return Ok(_mapper.Map>(models)); + } + + /// + /// Gets information from a single car history. + /// + /// + /// + [HttpGet("{id}", Name = "GetCarHistoryById")] + [ProducesResponseType(200, Type = typeof(CarHistoryDto))] + [ProducesResponseType(400)] + [ProducesResponseType(404)] + [ProducesResponseType(500)] + public async Task GetById(string id) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + var model = await _carHistoryRepository.FindOneAsync(id); + if (model == null) + { + return NotFound(); + } + + return Ok(_mapper.Map(model)); + } + + /// + /// Creates a new car history. + /// + /// + [HttpPost] + [ProducesResponseType(201)] + public async Task Post([FromBody] CarHistoryDto dto) + { + var input = _mapper.Map(dto); + var model = await _carHistoryRepository.CreateAsync(input); + return CreatedAtRoute("GetCarHistoryById", new { id = model.Id }, _mapper.Map(model)); + } + + /// + /// Updates a car history. + /// + /// + /// + [HttpPut("{id}")] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public IActionResult Put(string id, [FromBody] string value) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + throw new NotImplementedException(); + } + + /// + /// Deletes a car history. + /// + /// + /// + [HttpDelete("{id}")] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(string id) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + await _carHistoryRepository.DeleteAsync(id); + return NoContent(); + } + } +} diff --git a/dotnet/src/Api/Dto/CarHistoryDto.cs b/dotnet/src/Api/Dto/CarHistoryDto.cs new file mode 100644 index 00000000..c045e05c --- /dev/null +++ b/dotnet/src/Api/Dto/CarHistoryDto.cs @@ -0,0 +1,90 @@ +using System; + +namespace KeepTrack.Api.Dto +{ + /// + /// Car history data transfer object. + /// + public class CarHistoryDto + { + /// + /// History ID. + /// + public string Id { get; set; } + + /// + /// Car ID. + /// + public string CarId { get; set; } + + /// + /// History date. + /// + public DateTime HistoryDate { get; set; } + + /// + /// Mileage indicated on the car. + /// + public int Mileage { get; set; } + + /// + /// Action made on the car. + /// + public string Action { get; set; } + + /// + /// City. + /// + public string City { get; set; } + + /// + /// Longitude. + /// + public double? Longitude { get; set; } + + /// + /// Latitude. + /// + public double? Latitude { get; set; } + + /// + /// Fuel category. + /// + public string FuelCategory { get; set; } + + /// + /// Fuel volme (L). + /// + public double? FuelVolume { get; set; } + + /// + /// Fuel unit price. + /// + public double? FuelUnitPrice { get; set; } + + /// + /// Amount. + /// + public double? Amount { get; set; } + + /// + /// Is full tank? + /// + public bool? IsFullTank { get; set; } + + /// + /// Delta mileage since last refuel. + /// + public double? DeltaMileage { get; set; } + + /// + /// Last refuel history id. + /// + public string LastRefuelHistoryId { get; set; } + + /// + /// Station brand name. + /// + public string StationBrandName { get; set; } + } +} diff --git a/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs b/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs new file mode 100644 index 00000000..a3b9338e --- /dev/null +++ b/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs @@ -0,0 +1,28 @@ +using AutoMapper; + +namespace KeepTrack.Api.MappingProfiles +{ + /// + /// Car mapping profile. + /// + public class CarMappingProfile : Profile + { + /// + /// Profile name. + /// + public override string ProfileName + { + get { return "KeepTrackApiMappingProfile"; } + } + + /// + /// Create a new instance of . + /// + public CarMappingProfile() + { + CreateMap() + .ForMember(x => x.OwnerId, opt => opt.Ignore()); + CreateMap(); + } + } +} diff --git a/dotnet/src/Api/Program.cs b/dotnet/src/Api/Program.cs new file mode 100644 index 00000000..60cb7b13 --- /dev/null +++ b/dotnet/src/Api/Program.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace KeepTrack.Api +{ + /// + /// Application program. + /// + public static class Program + { + /// + /// Starting point. + /// + /// + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + /// + /// Create web application web host builder. + /// + /// + /// + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } + } +} diff --git a/dotnet/src/Api/Properties/launchSettings.json b/dotnet/src/Api/Properties/launchSettings.json new file mode 100644 index 00000000..70dcaadb --- /dev/null +++ b/dotnet/src/Api/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61193", + "sslPort": 44368 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Api": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/dotnet/src/Api/Startup.cs b/dotnet/src/Api/Startup.cs new file mode 100644 index 00000000..72e69b56 --- /dev/null +++ b/dotnet/src/Api/Startup.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Reflection; +using AutoMapper; +using KeepTrack.CarComponent.Infrastructure.MongoDb.DependencyInjection; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.OpenApi.Models; +using MongoDB.Bson; +using Withywoods.Dal.MongoDb.DependencyInjection; +using Withywoods.Dal.MongoDb.MappingConverters; + +namespace KeepTrack.Api +{ + /// + /// Application startup. + /// + public class Startup + { + private readonly AppConfiguration _configuration; + + /// + /// Create a new instance of . + /// + /// + public Startup(IConfiguration configuration) + { + _configuration = new AppConfiguration(configuration); + } + + /// + /// Configure services. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(_configuration.ConfigurationRoot) + .AddCarInfrastructureMongoDb() + .AddMongoDbContext(); + + ConfigureAutoMapper(services); + + // services + // .AddAuthentication(options => + // { + // options.DefaultScheme = ... + // options.DefaultChallengeScheme = ... + // }); + + services.AddControllers(); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc(_configuration.OpenApiInfo.Version, + new OpenApiInfo { Title = _configuration.OpenApiInfo.Title, Version = _configuration.OpenApiInfo.Version }); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); + }); + } + + /// + /// Configure the application pipeline. + /// + /// + /// + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSwagger(); + + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint($"/swagger/{_configuration.OpenApiInfo.Version}/swagger.json", _configuration.OpenApiInfo.Title); + }); + + app.UseHttpsRedirection(); + + app.UseRouting(); + + // app.UseAuthentication(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + + private static void ConfigureAutoMapper(IServiceCollection serviceCollection) + { + var mappingConfig = new MapperConfiguration(x => + { + // Infrastructure MongoDB + x.AddProfile(new CarComponent.Infrastructure.MongoDb.MappingProfiles.CarMappingProfile()); + x.CreateMap().ConvertUsing(); + x.CreateMap().ConvertUsing(); + // Api + x.AddProfile(new MappingProfiles.CarMappingProfile()); + // General + x.AllowNullCollections = true; + }); + var mapper = mappingConfig.CreateMapper(); + mapper.ConfigurationProvider.AssertConfigurationIsValid(); + serviceCollection.AddSingleton(mapper); + } + } +} diff --git a/dotnet/src/Api/appsettings.json b/dotnet/src/Api/appsettings.json new file mode 100644 index 00000000..b0371c27 --- /dev/null +++ b/dotnet/src/Api/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Infrastructure": { + "MongoDB": { + "DatabaseName": "inventory" + } + }, + "AllowedHosts": "*" +} diff --git a/dotnet/src/CarComponent.Domain/CarHistoryModel.cs b/dotnet/src/CarComponent.Domain/CarHistoryModel.cs index bdf5aad7..8758a3bf 100644 --- a/dotnet/src/CarComponent.Domain/CarHistoryModel.cs +++ b/dotnet/src/CarComponent.Domain/CarHistoryModel.cs @@ -1,11 +1,42 @@ using System; +using System.Collections.Generic; namespace KeepTrack.CarComponent.Domain { public class CarHistoryModel { public string Id { get; set; } + + public string OwnerId { get; set; } + public string CarId { get; set; } + public DateTime HistoryDate { get; set; } + + public int Mileage { get; set; } + + public string Action { get; set; } + + public string City { get; set; } + + public double? Longitude { get; set; } + + public double? Latitude { get; set; } + + public string FuelCategory { get; set; } + + public double? FuelVolume { get; set; } + + public double? FuelUnitPrice { get; set; } + + public double? Amount { get; set; } + + public bool? IsFullTank { get; set; } + + public double? DeltaMileage { get; set; } + + public string LastRefuelHistoryId { get; set; } + + public string StationBrandName { get; set; } } } diff --git a/dotnet/src/CarComponent.Domain/CarModel.cs b/dotnet/src/CarComponent.Domain/CarModel.cs index 8fa7ad59..e21ff7a4 100644 --- a/dotnet/src/CarComponent.Domain/CarModel.cs +++ b/dotnet/src/CarComponent.Domain/CarModel.cs @@ -3,6 +3,9 @@ public class CarModel { public string Id { get; set; } + + public string OwnerId { get; set; } + public string Name { get; set; } public override string ToString() diff --git a/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs b/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs index b97cf8b9..01bf7148 100644 --- a/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs @@ -6,6 +6,13 @@ namespace KeepTrack.CarComponent.Domain public interface ICarHistoryRepository { Task FindOneAsync(string id); + Task> FindAllAsync(string carId); + + Task CreateAsync(CarHistoryModel model); + + Task UpdateAsync(string id, CarHistoryModel model); + + Task DeleteAsync(string id); } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs index b0c4e622..d9fc176c 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/Car.cs @@ -6,6 +6,10 @@ public class Car { [BsonId] public string Id { get; set; } + + [BsonElement("owner_id")] + public string OwnerId { get; set; } + [BsonElement("commercial_name")] public string Name { get; set; } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs index 34326fc3..86febd2e 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs @@ -1,16 +1,40 @@ using System; +using System.Collections.Generic; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities { - public class CarHistory + public partial class CarHistory { [BsonId] public ObjectId Id { get; set; } + + [BsonElement("owner_id")] + public string OwnerId { get; set; } + [BsonElement("car_id")] public string CarId { get; set; } + [BsonElement("history_date")] public DateTime HistoryDate { get; set; } + + [BsonElement("mileage")] + public double Mileage { get; set; } + + [BsonElement("action")] + public string Action { get; set; } + + [BsonElement("location")] + public CarHistoryLocation Location { get; set; } + + [BsonElement("coordinates")] + public List Coordinates { get; set; } + + [BsonElement("fuel")] + public CarHistoryFuel Fuel { get; set; } + + [BsonElement("station")] + public CarHistoryStation Station { get; set; } } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryFuel.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryFuel.cs new file mode 100644 index 00000000..911d620a --- /dev/null +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryFuel.cs @@ -0,0 +1,29 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities +{ + public class CarHistoryFuel + { + [BsonElement("category")] + public string Category { get; set; } + + [BsonElement("volume")] + public double? Volume { get; set; } + + [BsonElement("unit_price")] + public double? UnitPrice { get; set; } + + [BsonElement("amount")] + public double? Amount { get; set; } + + [BsonElement("is_full_tank")] + public bool? IsFullTank { get; set; } + + [BsonElement("delta_mileage")] + public double? DeltaMileage { get; set; } + + [BsonElement("last_refuel_history_id")] + public ObjectId? LastRefuelHistoryId { get; set; } + } +} diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs new file mode 100644 index 00000000..97c1ee26 --- /dev/null +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs @@ -0,0 +1,11 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities +{ + public partial class CarHistoryLocation + { + [BsonElement("city")] + public string City { get; set; } + } +} diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs new file mode 100644 index 00000000..354b1ca8 --- /dev/null +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs @@ -0,0 +1,11 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities +{ + public partial class CarHistoryStation + { + [BsonElement("brand_name")] + public string BrandName { get; set; } + } +} diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs index f150d655..3d1273c2 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs @@ -1,4 +1,5 @@ -using AutoMapper; +using System.Collections.Generic; +using AutoMapper; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.MappingProfiles { @@ -12,7 +13,28 @@ public override string ProfileName public CarMappingProfile() { CreateMap(); - CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(x => x.City, opt => opt.MapFrom(x => x.Location != null ? x.Location.City : null)) + .ForMember(x => x.Longitude, opt => opt.MapFrom(x => x.Coordinates != null ? x.Coordinates[0] : (double?)null)) + .ForMember(x => x.Latitude, opt => opt.MapFrom(x => x.Coordinates != null ? x.Coordinates[1] : (double?)null)) + .ForMember(x => x.Amount, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.Amount : null)) + .ForMember(x => x.IsFullTank, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.IsFullTank : null)) + .ForMember(x => x.DeltaMileage, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.DeltaMileage : null)) + .ForMember(x => x.LastRefuelHistoryId, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.LastRefuelHistoryId : null)); + CreateMap() + .ForMember(x => x.Location, opt => opt.MapFrom(x => x)) + .ForMember(x => x.Coordinates, opt => opt.MapFrom(x => (x.Longitude.HasValue && x.Latitude.HasValue) ? new List { x.Longitude.Value, x.Latitude.Value } : null)) + .ForMember(x => x.Fuel, opt => opt.MapFrom(x => x)) + .ForMember(x => x.Station, opt => opt.MapFrom(x => x)); + CreateMap(); + CreateMap() + .ForMember(x => x.Category, opt => opt.MapFrom(x => x.FuelCategory)) + .ForMember(x => x.Volume, opt => opt.MapFrom(x => x.FuelVolume)) + .ForMember(x => x.UnitPrice, opt => opt.MapFrom(x => x.FuelUnitPrice)); + CreateMap() + .ForMember(x => x.BrandName, opt => opt.MapFrom(x => x.StationBrandName)); } } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs index ac14c0e2..f4509890 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs @@ -23,11 +23,7 @@ public CarHistoryRepository(IMongoDbContext mongoDbContext, ILogger FindOneAsync(string id) { - if (!ObjectId.TryParse(id, out var objectId)) - { - throw new ArgumentNullException(nameof(id), $"Cannot find the car history. \"{id}\" is not a valid id."); - } - + var objectId = ParseObjectId(id); var collection = GetCollection(); var dbEntries = await collection.FindAsync(x => x.Id == objectId); return Mapper.Map(dbEntries.FirstOrDefault()); @@ -39,5 +35,39 @@ public async Task> FindAllAsync(string carId) var dbEntries = await collection.FindAsync(x => x.CarId == carId); return Mapper.Map>(dbEntries.ToList()); } + + public async Task CreateAsync(CarHistoryModel model) + { + var collection = GetCollection(); + var entity = Mapper.Map(model); + await collection.InsertOneAsync(entity); + return Mapper.Map(entity); + } + + public async Task UpdateAsync(string id, CarHistoryModel model) + { + var objectId = ParseObjectId(id); + var collection = GetCollection(); + var entity = Mapper.Map(model); + var result = await collection.ReplaceOneAsync(x => x.Id == objectId, entity); + return result.ModifiedCount; + } + + public async Task DeleteAsync(string id) + { + var objectId = ParseObjectId(id); + var collection = GetCollection(); + var result = await collection.DeleteOneAsync(x => x.Id == objectId); + return result.DeletedCount; + } + + private ObjectId ParseObjectId(string id, string message = null) + { + if (!ObjectId.TryParse(id, out var objectId)) + { + throw new ArgumentNullException(nameof(id), $"{message}\"{id}\" is not a valid id."); + } + return objectId; + } } } diff --git a/dotnet/src/ConsoleApp/Program.cs b/dotnet/src/ConsoleApp/Program.cs index 7b1c6765..f7e0199d 100644 --- a/dotnet/src/ConsoleApp/Program.cs +++ b/dotnet/src/ConsoleApp/Program.cs @@ -53,23 +53,28 @@ private async static Task RunOptionsAndReturnExitCode(CommandLineOptions op using (var serviceProvider = CreateServiceProvider(configuration)) { - if (opts.Action == "CarDemo") + switch (opts.Action) { - var id = opts.Id; + case "CarDemo": + var id = opts.Id; - LogVerbose(opts, "Query the car collection"); + LogVerbose(opts, "Query the car collection"); - var carRepository = serviceProvider.GetService(); - var car = await carRepository.FindOneAsync(id); + var carRepository = serviceProvider.GetService(); + var car = await carRepository.FindOneAsync(id); - Console.WriteLine($"Car found: {car}"); + Console.WriteLine($"Car found: {car}"); - LogVerbose(opts, "Query the car history collection"); + LogVerbose(opts, "Query the car history collection"); - var carHistoryRepository = serviceProvider.GetService(); - var history = await carHistoryRepository.FindAllAsync(id); + var carHistoryRepository = serviceProvider.GetService(); + var history = await carHistoryRepository.FindAllAsync(id); - Console.WriteLine($"Car history found: {history.Count}"); + Console.WriteLine($"Car history found: {history.Count}"); + break; + default: + Console.WriteLine($"Unknown action \"{opts.Action}\""); + return -1; } return 0; diff --git a/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj b/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj new file mode 100644 index 00000000..078dfab1 --- /dev/null +++ b/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + + + + + From adef0bbe80444c8fd4470e0611559b0b0fe9e61e Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Wed, 23 Oct 2019 17:14:21 +0200 Subject: [PATCH 3/6] Activate authentication with Firebase --- dotnet/src/Api/Api.csproj | 1 + .../Api/Controllers/CarHistoryController.cs | 32 +++++++++---- dotnet/src/Api/Startup.cs | 48 +++++++++++++++---- dotnet/src/Api/appsettings.json | 11 ++++- .../CarComponent.Domain/CarHistoryModel.cs | 1 - .../ICarHistoryRepository.cs | 8 ++-- .../Entities/CarHistoryLocation.cs | 3 +- .../Entities/CarHistoryStation.cs | 3 +- .../Repositories/CarHistoryRepository.cs | 16 +++---- dotnet/src/ConsoleApp/Program.cs | 4 +- .../CarComponent.Domain.UnitTests.csproj | 14 ++++-- 11 files changed, 99 insertions(+), 42 deletions(-) diff --git a/dotnet/src/Api/Api.csproj b/dotnet/src/Api/Api.csproj index e0d7e086..56e5e465 100644 --- a/dotnet/src/Api/Api.csproj +++ b/dotnet/src/Api/Api.csproj @@ -21,6 +21,7 @@ + diff --git a/dotnet/src/Api/Controllers/CarHistoryController.cs b/dotnet/src/Api/Controllers/CarHistoryController.cs index 9e311837..5b303f9f 100644 --- a/dotnet/src/Api/Controllers/CarHistoryController.cs +++ b/dotnet/src/Api/Controllers/CarHistoryController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AutoMapper; using KeepTrack.Api.Dto; @@ -13,7 +14,7 @@ namespace Api.Controllers /// Car history controller. /// [ApiController] - // [Authorize()] + [Authorize] [Route("api/car-history")] public class CarHistoryController : ControllerBase { @@ -36,7 +37,6 @@ public CarHistoryController(IMapper mapper, ICarHistoryRepository carHistoryRepo /// /// Car ID /// - // GET: api/CarHistory [HttpGet] [ProducesResponseType(200, Type = typeof(List))] [ProducesResponseType(400)] @@ -48,7 +48,7 @@ public async Task Get(string carId) return BadRequest(); } - var models = await _carHistoryRepository.FindAllAsync(carId); + var models = await _carHistoryRepository.FindAllAsync(carId, GetUserId()); return Ok(_mapper.Map>(models)); } @@ -69,7 +69,7 @@ public async Task GetById(string id) return BadRequest(); } - var model = await _carHistoryRepository.FindOneAsync(id); + var model = await _carHistoryRepository.FindOneAsync(id, GetUserId()); if (model == null) { return NotFound(); @@ -87,6 +87,7 @@ public async Task GetById(string id) public async Task Post([FromBody] CarHistoryDto dto) { var input = _mapper.Map(dto); + input.OwnerId = GetUserId(); var model = await _carHistoryRepository.CreateAsync(input); return CreatedAtRoute("GetCarHistoryById", new { id = model.Id }, _mapper.Map(model)); } @@ -95,18 +96,22 @@ public async Task Post([FromBody] CarHistoryDto dto) /// Updates a car history. /// /// - /// + /// [HttpPut("{id}")] + [ProducesResponseType(204)] [ProducesResponseType(400)] [ProducesResponseType(500)] - public IActionResult Put(string id, [FromBody] string value) + public async Task Put(string id, [FromBody] CarHistoryDto dto) { if (string.IsNullOrEmpty(id)) { return BadRequest(); } - throw new NotImplementedException(); + var input = _mapper.Map(dto); + input.OwnerId = GetUserId(); + await _carHistoryRepository.UpdateAsync(id, input, GetUserId()); + return NoContent(); } /// @@ -125,8 +130,19 @@ public async Task Delete(string id) return BadRequest(); } - await _carHistoryRepository.DeleteAsync(id); + await _carHistoryRepository.DeleteAsync(id, GetUserId()); return NoContent(); } + + private string GetUserId() + { + var userId = User.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; + if (string.IsNullOrEmpty(userId)) + { + throw new UnauthorizedAccessException(); + } + + return userId; + } } } diff --git a/dotnet/src/Api/Startup.cs b/dotnet/src/Api/Startup.cs index 72e69b56..6daa4ef4 100644 --- a/dotnet/src/Api/Startup.cs +++ b/dotnet/src/Api/Startup.cs @@ -3,15 +3,14 @@ using System.Reflection; using AutoMapper; using KeepTrack.CarComponent.Infrastructure.MongoDb.DependencyInjection; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using MongoDB.Bson; using Withywoods.Dal.MongoDb.DependencyInjection; @@ -47,12 +46,22 @@ public void ConfigureServices(IServiceCollection services) ConfigureAutoMapper(services); - // services - // .AddAuthentication(options => - // { - // options.DefaultScheme = ... - // options.DefaultChallengeScheme = ... - // }); + IdentityModelEventSource.ShowPII = true; + + services + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = _configuration.ConfigurationRoot["Authentication:JwtBearer:Authority"]; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = _configuration.ConfigurationRoot["Authentication:JwtBearer:TokenValidation:Issuer"], + ValidateAudience = true, + ValidAudience = _configuration.ConfigurationRoot["Authentication:JwtBearer:TokenValidation:Audience"], + ValidateLifetime = true + }; + }); services.AddControllers(); @@ -61,6 +70,25 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc(_configuration.OpenApiInfo.Version, new OpenApiInfo { Title = _configuration.OpenApiInfo.Title, Version = _configuration.OpenApiInfo.Version }); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new[] { "readAccess", "writeAccess" } + } + }); + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); @@ -90,7 +118,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); - // app.UseAuthentication(); + app.UseAuthentication(); app.UseAuthorization(); diff --git a/dotnet/src/Api/appsettings.json b/dotnet/src/Api/appsettings.json index b0371c27..e32f0c59 100644 --- a/dotnet/src/Api/appsettings.json +++ b/dotnet/src/Api/appsettings.json @@ -11,5 +11,14 @@ "DatabaseName": "inventory" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Authentication": { + "JwtBearer": { + "Authority": "", + "TokenValidation": { + "Issuer": "", + "Audience": "" + } + } + } } diff --git a/dotnet/src/CarComponent.Domain/CarHistoryModel.cs b/dotnet/src/CarComponent.Domain/CarHistoryModel.cs index 8758a3bf..10b9cde0 100644 --- a/dotnet/src/CarComponent.Domain/CarHistoryModel.cs +++ b/dotnet/src/CarComponent.Domain/CarHistoryModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace KeepTrack.CarComponent.Domain { diff --git a/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs b/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs index 01bf7148..9d5d4a16 100644 --- a/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Domain/ICarHistoryRepository.cs @@ -5,14 +5,14 @@ namespace KeepTrack.CarComponent.Domain { public interface ICarHistoryRepository { - Task FindOneAsync(string id); + Task FindOneAsync(string id, string ownerId); - Task> FindAllAsync(string carId); + Task> FindAllAsync(string carId, string ownerId); Task CreateAsync(CarHistoryModel model); - Task UpdateAsync(string id, CarHistoryModel model); + Task UpdateAsync(string id, CarHistoryModel model, string ownerId); - Task DeleteAsync(string id); + Task DeleteAsync(string id, string ownerId); } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs index 97c1ee26..d91a4237 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryLocation.cs @@ -1,5 +1,4 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Attributes; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities { diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs index 354b1ca8..18f99958 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistoryStation.cs @@ -1,5 +1,4 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Attributes; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities { diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs index f4509890..9e66ce04 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs @@ -21,18 +21,18 @@ public CarHistoryRepository(IMongoDbContext mongoDbContext, ILogger "car_history"; - public async Task FindOneAsync(string id) + public async Task FindOneAsync(string id, string ownerId) { var objectId = ParseObjectId(id); var collection = GetCollection(); - var dbEntries = await collection.FindAsync(x => x.Id == objectId); + var dbEntries = await collection.FindAsync(x => x.Id == objectId && x.OwnerId == ownerId); return Mapper.Map(dbEntries.FirstOrDefault()); } - public async Task> FindAllAsync(string carId) + public async Task> FindAllAsync(string carId, string ownerId) { var collection = GetCollection(); - var dbEntries = await collection.FindAsync(x => x.CarId == carId); + var dbEntries = await collection.FindAsync(x => x.CarId == carId && x.OwnerId == ownerId); return Mapper.Map>(dbEntries.ToList()); } @@ -44,20 +44,20 @@ public async Task CreateAsync(CarHistoryModel model) return Mapper.Map(entity); } - public async Task UpdateAsync(string id, CarHistoryModel model) + public async Task UpdateAsync(string id, CarHistoryModel model, string ownerId) { var objectId = ParseObjectId(id); var collection = GetCollection(); var entity = Mapper.Map(model); - var result = await collection.ReplaceOneAsync(x => x.Id == objectId, entity); + var result = await collection.ReplaceOneAsync(x => x.Id == objectId && x.OwnerId == ownerId, entity); return result.ModifiedCount; } - public async Task DeleteAsync(string id) + public async Task DeleteAsync(string id, string ownerId) { var objectId = ParseObjectId(id); var collection = GetCollection(); - var result = await collection.DeleteOneAsync(x => x.Id == objectId); + var result = await collection.DeleteOneAsync(x => x.Id == objectId && x.OwnerId == ownerId); return result.DeletedCount; } diff --git a/dotnet/src/ConsoleApp/Program.cs b/dotnet/src/ConsoleApp/Program.cs index f7e0199d..e3432b16 100644 --- a/dotnet/src/ConsoleApp/Program.cs +++ b/dotnet/src/ConsoleApp/Program.cs @@ -55,7 +55,7 @@ private async static Task RunOptionsAndReturnExitCode(CommandLineOptions op { switch (opts.Action) { - case "CarDemo": + case "CarDemo": var id = opts.Id; LogVerbose(opts, "Query the car collection"); @@ -68,7 +68,7 @@ private async static Task RunOptionsAndReturnExitCode(CommandLineOptions op LogVerbose(opts, "Query the car history collection"); var carHistoryRepository = serviceProvider.GetService(); - var history = await carHistoryRepository.FindAllAsync(id); + var history = await carHistoryRepository.FindAllAsync(id, "xxxx"); Console.WriteLine($"Car history found: {history.Count}"); break; diff --git a/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj b/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj index 078dfab1..781da62b 100644 --- a/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj +++ b/dotnet/test/CarComponent.Domain.UnitTests/CarComponent.Domain.UnitTests.csproj @@ -7,10 +7,16 @@ - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 851b6c1e36625ef7f34c28585f4af4e0b27be46b Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Thu, 24 Oct 2019 16:48:53 +0200 Subject: [PATCH 4/6] Add MovieComponent --- dotnet/KeepTrack.sln | 14 ++ dotnet/src/Api/Api.csproj | 2 + .../Api/Controllers/CarHistoryController.cs | 17 +-- dotnet/src/Api/Controllers/ControllerBase.cs | 26 ++++ dotnet/src/Api/Controllers/MovieController.cs | 129 ++++++++++++++++++ dotnet/src/Api/Dto/MovieDto.cs | 18 +++ dotnet/src/Api/Filters/ExceptionFilter.cs | 43 ++++++ .../Api/MappingProfiles/CarMappingProfile.cs | 2 +- .../MappingProfiles/MovieMappingProfile.cs | 28 ++++ dotnet/src/Api/Startup.cs | 104 ++++++++------ ...CarComponent.Infrastructure.MongoDb.csproj | 5 +- .../Entities/CarHistory.cs | 3 +- .../Repositories/CarHistoryRepository.cs | 50 +------ dotnet/src/Dal.MongoDb/Dal.MongoDb.csproj | 3 +- dotnet/src/Dal.MongoDb/Entities/IEntity.cs | 10 ++ .../Repositories/RepositoryBase.cs | 61 +++++++++ .../MovieComponent.Domain/IMovieRepository.cs | 18 +++ .../MovieComponent.Domain.csproj | 15 ++ .../src/MovieComponent.Domain/MovieModel.cs | 11 ++ .../ServiceCollectionExtensions.cs | 21 +++ .../Entities/Movie.cs | 18 +++ .../MappingProfiles/MovieMappingProfile.cs | 18 +++ ...vieComponent.Infrastructure.MongoDb.csproj | 20 +++ .../Repositories/MovieRepository.cs | 29 ++++ 24 files changed, 550 insertions(+), 115 deletions(-) create mode 100644 dotnet/src/Api/Controllers/ControllerBase.cs create mode 100644 dotnet/src/Api/Controllers/MovieController.cs create mode 100644 dotnet/src/Api/Dto/MovieDto.cs create mode 100644 dotnet/src/Api/Filters/ExceptionFilter.cs create mode 100644 dotnet/src/Api/MappingProfiles/MovieMappingProfile.cs create mode 100644 dotnet/src/Dal.MongoDb/Entities/IEntity.cs create mode 100644 dotnet/src/Dal.MongoDb/Repositories/RepositoryBase.cs create mode 100644 dotnet/src/MovieComponent.Domain/IMovieRepository.cs create mode 100644 dotnet/src/MovieComponent.Domain/MovieComponent.Domain.csproj create mode 100644 dotnet/src/MovieComponent.Domain/MovieModel.cs create mode 100644 dotnet/src/MovieComponent.Infrastructure.MongoDb/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 dotnet/src/MovieComponent.Infrastructure.MongoDb/Entities/Movie.cs create mode 100644 dotnet/src/MovieComponent.Infrastructure.MongoDb/MappingProfiles/MovieMappingProfile.cs create mode 100644 dotnet/src/MovieComponent.Infrastructure.MongoDb/MovieComponent.Infrastructure.MongoDb.csproj create mode 100644 dotnet/src/MovieComponent.Infrastructure.MongoDb/Repositories/MovieRepository.cs diff --git a/dotnet/KeepTrack.sln b/dotnet/KeepTrack.sln index f46442a7..4fd01ba3 100644 --- a/dotnet/KeepTrack.sln +++ b/dotnet/KeepTrack.sln @@ -31,6 +31,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "src\Api\Api.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarComponent.Domain.UnitTests", "test\CarComponent.Domain.UnitTests\CarComponent.Domain.UnitTests.csproj", "{2079D00A-8489-4A43-BC2E-0B7CFB772CE7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieComponent.Domain", "src\MovieComponent.Domain\MovieComponent.Domain.csproj", "{A08241ED-8F29-4DF7-9BCF-2E310A9C8187}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieComponent.Infrastructure.MongoDb", "src\MovieComponent.Infrastructure.MongoDb\MovieComponent.Infrastructure.MongoDb.csproj", "{E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +65,14 @@ Global {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU {2079D00A-8489-4A43-BC2E-0B7CFB772CE7}.Release|Any CPU.Build.0 = Release|Any CPU + {A08241ED-8F29-4DF7-9BCF-2E310A9C8187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A08241ED-8F29-4DF7-9BCF-2E310A9C8187}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A08241ED-8F29-4DF7-9BCF-2E310A9C8187}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A08241ED-8F29-4DF7-9BCF-2E310A9C8187}.Release|Any CPU.Build.0 = Release|Any CPU + {E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -74,6 +86,8 @@ Global {F7250DAA-718B-44FB-990B-AD7DFC488AA7} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} {5B65EF04-3F7A-4042-8D8B-DFB3AD7CC560} = {61C528CD-816E-4FC5-ADFA-C5C25A3C4052} {2079D00A-8489-4A43-BC2E-0B7CFB772CE7} = {A52EF787-A7DD-4F2F-99B1-9A62CBE1DE5B} + {A08241ED-8F29-4DF7-9BCF-2E310A9C8187} = {A6A8275A-6360-43E7-AB5A-D8A1E96BED06} + {E7BCC402-E50E-4AA1-AA7C-1A9C95DBDB50} = {A6A8275A-6360-43E7-AB5A-D8A1E96BED06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {876333E7-72D4-4F43-B6F9-9432CA2B7425} diff --git a/dotnet/src/Api/Api.csproj b/dotnet/src/Api/Api.csproj index 56e5e465..f4757386 100644 --- a/dotnet/src/Api/Api.csproj +++ b/dotnet/src/Api/Api.csproj @@ -28,6 +28,8 @@ + + diff --git a/dotnet/src/Api/Controllers/CarHistoryController.cs b/dotnet/src/Api/Controllers/CarHistoryController.cs index 5b303f9f..9bd87654 100644 --- a/dotnet/src/Api/Controllers/CarHistoryController.cs +++ b/dotnet/src/Api/Controllers/CarHistoryController.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using KeepTrack.Api.Dto; @@ -16,7 +14,7 @@ namespace Api.Controllers [ApiController] [Authorize] [Route("api/car-history")] - public class CarHistoryController : ControllerBase + public class CarHistoryController : KeepTrack.Api.Controllers.ControllerBase { private readonly IMapper _mapper; private readonly ICarHistoryRepository _carHistoryRepository; @@ -133,16 +131,5 @@ public async Task Delete(string id) await _carHistoryRepository.DeleteAsync(id, GetUserId()); return NoContent(); } - - private string GetUserId() - { - var userId = User.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; - if (string.IsNullOrEmpty(userId)) - { - throw new UnauthorizedAccessException(); - } - - return userId; - } } } diff --git a/dotnet/src/Api/Controllers/ControllerBase.cs b/dotnet/src/Api/Controllers/ControllerBase.cs new file mode 100644 index 00000000..4a8db0f9 --- /dev/null +++ b/dotnet/src/Api/Controllers/ControllerBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; + +namespace KeepTrack.Api.Controllers +{ + /// + /// Base controller for the web application. + /// + public abstract class ControllerBase : Microsoft.AspNetCore.Mvc.ControllerBase + { + /// + /// Get authenticated user id. + /// + /// + protected string GetUserId() + { + var userId = User.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; + if (string.IsNullOrEmpty(userId)) + { + throw new UnauthorizedAccessException(); + } + + return userId; + } + } +} diff --git a/dotnet/src/Api/Controllers/MovieController.cs b/dotnet/src/Api/Controllers/MovieController.cs new file mode 100644 index 00000000..0d2a3101 --- /dev/null +++ b/dotnet/src/Api/Controllers/MovieController.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using KeepTrack.Api.Dto; +using KeepTrack.MovieComponent.Domain; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace KeepTrack.Api.Controllers +{ + /// + /// Movie controller. + /// + [ApiController] + [Authorize] + [Route("api/movies")] + public class MovieController : KeepTrack.Api.Controllers.ControllerBase + { + private readonly IMapper _mapper; + private readonly IMovieRepository _movieRepository; + + /// + /// Creates a new instance of . + /// + /// + /// + public MovieController(IMapper mapper, IMovieRepository movieRepository) + { + _mapper = mapper; + _movieRepository = movieRepository; + } + + /// + /// Gets all the movies. + /// + /// + [HttpGet] + [ProducesResponseType(200, Type = typeof(List))] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Get() + { + var models = await _movieRepository.FindAllAsync(GetUserId()); + return Ok(_mapper.Map>(models)); + } + + /// + /// Gets information from a single movie. + /// + /// + /// + [HttpGet("{id}", Name = "GetMovieById")] + [ProducesResponseType(200, Type = typeof(MovieDto))] + [ProducesResponseType(400)] + [ProducesResponseType(404)] + [ProducesResponseType(500)] + public async Task GetById(string id) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + var model = await _movieRepository.FindOneAsync(id, GetUserId()); + if (model == null) + { + return NotFound(); + } + + return Ok(_mapper.Map(model)); + } + + /// + /// Creates a new car history. + /// + /// + [HttpPost] + [ProducesResponseType(201)] + public async Task Post([FromBody] MovieDto dto) + { + var input = _mapper.Map(dto); + input.OwnerId = GetUserId(); + var model = await _movieRepository.CreateAsync(input); + return CreatedAtRoute("GetMovieById", new { id = model.Id }, _mapper.Map(model)); + } + + /// + /// Updates a movie. + /// + /// + /// + [HttpPut("{id}")] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Put(string id, [FromBody] MovieDto dto) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + var input = _mapper.Map(dto); + input.OwnerId = GetUserId(); + await _movieRepository.UpdateAsync(id, input, GetUserId()); + return NoContent(); + } + + /// + /// Deletes a movie. + /// + /// + /// + [HttpDelete("{id}")] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(string id) + { + if (string.IsNullOrEmpty(id)) + { + return BadRequest(); + } + + await _movieRepository.DeleteAsync(id, GetUserId()); + return NoContent(); + } + } +} diff --git a/dotnet/src/Api/Dto/MovieDto.cs b/dotnet/src/Api/Dto/MovieDto.cs new file mode 100644 index 00000000..4677d7ce --- /dev/null +++ b/dotnet/src/Api/Dto/MovieDto.cs @@ -0,0 +1,18 @@ +namespace KeepTrack.Api.Dto +{ + /// + /// Movie data transfer object. + /// + public class MovieDto + { + /// + /// Movie ID. + /// + public string Id { get; set; } + + /// + /// Title. + /// + public string Title { get; set; } + } +} diff --git a/dotnet/src/Api/Filters/ExceptionFilter.cs b/dotnet/src/Api/Filters/ExceptionFilter.cs new file mode 100644 index 00000000..34e33056 --- /dev/null +++ b/dotnet/src/Api/Filters/ExceptionFilter.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace KeepTrack.Api.Filters +{ + /// + /// Exception filter to make sure the + /// + public sealed class ExceptionFilter : ExceptionFilterAttribute + { + /// + /// Create a new instance of . + /// + public ExceptionFilter() + { + } + + /// + /// Review when an exception is raised. + /// + /// + public override void OnException(ExceptionContext context) + { + switch (context.Exception) + { + case ArgumentNullException argumentNullException: + context.Result = new JsonResult(argumentNullException.Message); + context.HttpContext.Response.StatusCode = 400; + break; + case ArgumentException argumentException: + context.Result = new JsonResult(argumentException.Message); + context.HttpContext.Response.StatusCode = 400; + break; + default: + context.Result = new JsonResult(context.Exception.Message); + context.HttpContext.Response.StatusCode = 500; + break; + } + base.OnException(context); + } + } +} diff --git a/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs b/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs index a3b9338e..6dedefc4 100644 --- a/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs +++ b/dotnet/src/Api/MappingProfiles/CarMappingProfile.cs @@ -12,7 +12,7 @@ public class CarMappingProfile : Profile /// public override string ProfileName { - get { return "KeepTrackApiMappingProfile"; } + get { return "KeepTrackApiCarMappingProfile"; } } /// diff --git a/dotnet/src/Api/MappingProfiles/MovieMappingProfile.cs b/dotnet/src/Api/MappingProfiles/MovieMappingProfile.cs new file mode 100644 index 00000000..5db0d3e2 --- /dev/null +++ b/dotnet/src/Api/MappingProfiles/MovieMappingProfile.cs @@ -0,0 +1,28 @@ +using AutoMapper; + +namespace KeepTrack.Api.MappingProfiles +{ + /// + /// Movie mapping profile. + /// + public class MovieMappingProfile : Profile + { + /// + /// Profile name. + /// + public override string ProfileName + { + get { return "KeepTrackApiMovieMappingProfile"; } + } + + /// + /// Create a new instance of . + /// + public MovieMappingProfile() + { + CreateMap() + .ForMember(x => x.OwnerId, opt => opt.Ignore()); + CreateMap(); + } + } +} diff --git a/dotnet/src/Api/Startup.cs b/dotnet/src/Api/Startup.cs index 6daa4ef4..4cdccaa2 100644 --- a/dotnet/src/Api/Startup.cs +++ b/dotnet/src/Api/Startup.cs @@ -3,13 +3,13 @@ using System.Reflection; using AutoMapper; using KeepTrack.CarComponent.Infrastructure.MongoDb.DependencyInjection; +using KeepTrack.MovieComponent.Infrastructure.MongoDb.DependencyInjection; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using MongoDB.Bson; @@ -42,57 +42,19 @@ public void ConfigureServices(IServiceCollection services) { services.AddSingleton(_configuration.ConfigurationRoot) .AddCarInfrastructureMongoDb() + .AddMovieInfrastructureMongoDb() .AddMongoDbContext(); ConfigureAutoMapper(services); - IdentityModelEventSource.ShowPII = true; + ConfigureAuthentication(services, _configuration.ConfigurationRoot); - services - .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.Authority = _configuration.ConfigurationRoot["Authentication:JwtBearer:Authority"]; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = _configuration.ConfigurationRoot["Authentication:JwtBearer:TokenValidation:Issuer"], - ValidateAudience = true, - ValidAudience = _configuration.ConfigurationRoot["Authentication:JwtBearer:TokenValidation:Audience"], - ValidateLifetime = true - }; - }); - - services.AddControllers(); - - services.AddSwaggerGen(c => + services.AddControllers(opts => { - c.SwaggerDoc(_configuration.OpenApiInfo.Version, - new OpenApiInfo { Title = _configuration.OpenApiInfo.Title, Version = _configuration.OpenApiInfo.Version }); - - c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", - Name = "Authorization", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey - }); - - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } - }, - new[] { "readAccess", "writeAccess" } - } - }); - - var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - c.IncludeXmlComments(xmlPath); + opts.Filters.Add(); }); + + ConfigureSwagger(services, _configuration.OpenApiInfo); } /// @@ -134,10 +96,12 @@ private static void ConfigureAutoMapper(IServiceCollection serviceCollection) { // Infrastructure MongoDB x.AddProfile(new CarComponent.Infrastructure.MongoDb.MappingProfiles.CarMappingProfile()); + x.AddProfile(new MovieComponent.Infrastructure.MongoDb.MappingProfiles.MovieMappingProfile()); x.CreateMap().ConvertUsing(); x.CreateMap().ConvertUsing(); // Api x.AddProfile(new MappingProfiles.CarMappingProfile()); + x.AddProfile(new MappingProfiles.MovieMappingProfile()); // General x.AllowNullCollections = true; }); @@ -145,5 +109,55 @@ private static void ConfigureAutoMapper(IServiceCollection serviceCollection) mapper.ConfigurationProvider.AssertConfigurationIsValid(); serviceCollection.AddSingleton(mapper); } + + private static void ConfigureAuthentication(IServiceCollection serviceCollection, IConfiguration configuration) + { + serviceCollection + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = configuration["Authentication:JwtBearer:Authority"]; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = configuration["Authentication:JwtBearer:TokenValidation:Issuer"], + ValidateAudience = true, + ValidAudience = configuration["Authentication:JwtBearer:TokenValidation:Audience"], + ValidateLifetime = true + }; + }); + } + + private static void ConfigureSwagger(IServiceCollection serviceCollection, OpenApiInfo openApiInfo) + { + serviceCollection.AddSwaggerGen(c => + { + c.SwaggerDoc(openApiInfo.Version, + new OpenApiInfo { Title = openApiInfo.Title, Version = openApiInfo.Version }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new[] { "readAccess", "writeAccess" } + } + }); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); + }); + } } } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/CarComponent.Infrastructure.MongoDb.csproj b/dotnet/src/CarComponent.Infrastructure.MongoDb/CarComponent.Infrastructure.MongoDb.csproj index 59209cd5..ab1454e8 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/CarComponent.Infrastructure.MongoDb.csproj +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/CarComponent.Infrastructure.MongoDb.csproj @@ -12,12 +12,9 @@ true - - - - + diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs index 86febd2e..a27cb2e7 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Entities/CarHistory.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using KeepTrack.Dal.MongoDb.Entities; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace KeepTrack.CarComponent.Infrastructure.MongoDb.Entities { - public partial class CarHistory + public partial class CarHistory : IEntity { [BsonId] public ObjectId Id { get; set; } diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs index 9e66ce04..ff443014 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/Repositories/CarHistoryRepository.cs @@ -1,18 +1,16 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using KeepTrack.CarComponent.Domain; using KeepTrack.CarComponent.Infrastructure.MongoDb.Entities; +using KeepTrack.Dal.MongoDb.Repositories; using Microsoft.Extensions.Logging; -using MongoDB.Bson; using MongoDB.Driver; using Withywoods.Dal.MongoDb; -using Withywoods.Dal.MongoDb.Repositories; namespace KeepTrack.CarComponent.Infrastructure.Repositories { - public class CarHistoryRepository : RepositoryBase, ICarHistoryRepository + public class CarHistoryRepository : RepositoryBase, ICarHistoryRepository { public CarHistoryRepository(IMongoDbContext mongoDbContext, ILogger logger, IMapper mapper) : base(mongoDbContext, logger, mapper) @@ -21,53 +19,11 @@ public CarHistoryRepository(IMongoDbContext mongoDbContext, ILogger "car_history"; - public async Task FindOneAsync(string id, string ownerId) - { - var objectId = ParseObjectId(id); - var collection = GetCollection(); - var dbEntries = await collection.FindAsync(x => x.Id == objectId && x.OwnerId == ownerId); - return Mapper.Map(dbEntries.FirstOrDefault()); - } - public async Task> FindAllAsync(string carId, string ownerId) { var collection = GetCollection(); var dbEntries = await collection.FindAsync(x => x.CarId == carId && x.OwnerId == ownerId); return Mapper.Map>(dbEntries.ToList()); } - - public async Task CreateAsync(CarHistoryModel model) - { - var collection = GetCollection(); - var entity = Mapper.Map(model); - await collection.InsertOneAsync(entity); - return Mapper.Map(entity); - } - - public async Task UpdateAsync(string id, CarHistoryModel model, string ownerId) - { - var objectId = ParseObjectId(id); - var collection = GetCollection(); - var entity = Mapper.Map(model); - var result = await collection.ReplaceOneAsync(x => x.Id == objectId && x.OwnerId == ownerId, entity); - return result.ModifiedCount; - } - - public async Task DeleteAsync(string id, string ownerId) - { - var objectId = ParseObjectId(id); - var collection = GetCollection(); - var result = await collection.DeleteOneAsync(x => x.Id == objectId && x.OwnerId == ownerId); - return result.DeletedCount; - } - - private ObjectId ParseObjectId(string id, string message = null) - { - if (!ObjectId.TryParse(id, out var objectId)) - { - throw new ArgumentNullException(nameof(id), $"{message}\"{id}\" is not a valid id."); - } - return objectId; - } } } diff --git a/dotnet/src/Dal.MongoDb/Dal.MongoDb.csproj b/dotnet/src/Dal.MongoDb/Dal.MongoDb.csproj index c458d657..c885981b 100644 --- a/dotnet/src/Dal.MongoDb/Dal.MongoDb.csproj +++ b/dotnet/src/Dal.MongoDb/Dal.MongoDb.csproj @@ -13,8 +13,7 @@ - - + diff --git a/dotnet/src/Dal.MongoDb/Entities/IEntity.cs b/dotnet/src/Dal.MongoDb/Entities/IEntity.cs new file mode 100644 index 00000000..5b82df15 --- /dev/null +++ b/dotnet/src/Dal.MongoDb/Entities/IEntity.cs @@ -0,0 +1,10 @@ +using MongoDB.Bson; + +namespace KeepTrack.Dal.MongoDb.Entities +{ + public interface IEntity + { + ObjectId Id { get; set; } + string OwnerId { get; set; } + } +} diff --git a/dotnet/src/Dal.MongoDb/Repositories/RepositoryBase.cs b/dotnet/src/Dal.MongoDb/Repositories/RepositoryBase.cs new file mode 100644 index 00000000..5ce011f8 --- /dev/null +++ b/dotnet/src/Dal.MongoDb/Repositories/RepositoryBase.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using KeepTrack.Dal.MongoDb.Entities; +using Microsoft.Extensions.Logging; +using MongoDB.Bson; +using MongoDB.Driver; +using Withywoods.Dal.MongoDb; + +namespace KeepTrack.Dal.MongoDb.Repositories +{ + public abstract class RepositoryBase : Withywoods.Dal.MongoDb.Repositories.RepositoryBase where U : IEntity + { + protected RepositoryBase(IMongoDbContext mongoDbContext, ILogger> logger, IMapper mapper) + : base(mongoDbContext, logger, mapper) + { + } + + public async Task FindOneAsync(string id, string ownerId) + { + var objectId = ParseObjectId(id); + var collection = GetCollection(); + var dbEntries = await collection.FindAsync(x => x.Id == objectId && x.OwnerId == ownerId); + return Mapper.Map(dbEntries.FirstOrDefault()); + } + + public async Task CreateAsync(T model) + { + var collection = GetCollection(); + var entity = Mapper.Map(model); + await collection.InsertOneAsync(entity); + return Mapper.Map(entity); + } + + public async Task UpdateAsync(string id, T model, string ownerId) + { + var objectId = ParseObjectId(id); + var collection = GetCollection(); + var entity = Mapper.Map(model); + var result = await collection.ReplaceOneAsync(x => x.Id == objectId && x.OwnerId == ownerId, entity); + return result.ModifiedCount; + } + + public async Task DeleteAsync(string id, string ownerId) + { + var objectId = ParseObjectId(id); + var collection = GetCollection(); + var result = await collection.DeleteOneAsync(x => x.Id == objectId && x.OwnerId == ownerId); + return result.DeletedCount; + } + + protected static ObjectId ParseObjectId(string id, string message = null) + { + if (string.IsNullOrEmpty(id) || !ObjectId.TryParse(id, out var objectId)) + { + throw new ArgumentException($"{message}{id} is not a valid id.", nameof(id)); + } + return objectId; + } + } +} diff --git a/dotnet/src/MovieComponent.Domain/IMovieRepository.cs b/dotnet/src/MovieComponent.Domain/IMovieRepository.cs new file mode 100644 index 00000000..f3e04687 --- /dev/null +++ b/dotnet/src/MovieComponent.Domain/IMovieRepository.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace KeepTrack.MovieComponent.Domain +{ + public interface IMovieRepository + { + Task FindOneAsync(string id, string ownerId); + + Task> FindAllAsync(string ownerId); + + Task CreateAsync(MovieModel model); + + Task UpdateAsync(string id, MovieModel model, string ownerId); + + Task DeleteAsync(string id, string ownerId); + } +} diff --git a/dotnet/src/MovieComponent.Domain/MovieComponent.Domain.csproj b/dotnet/src/MovieComponent.Domain/MovieComponent.Domain.csproj new file mode 100644 index 00000000..753689c0 --- /dev/null +++ b/dotnet/src/MovieComponent.Domain/MovieComponent.Domain.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.1 + KeepTrack.MovieComponent.Domain + KeepTrack.MovieComponent.Domain + {FAD8CE20-6164-4986-B3C1-6DF1EFF3C58B} + + + + full + true + + + diff --git a/dotnet/src/MovieComponent.Domain/MovieModel.cs b/dotnet/src/MovieComponent.Domain/MovieModel.cs new file mode 100644 index 00000000..3926abc4 --- /dev/null +++ b/dotnet/src/MovieComponent.Domain/MovieModel.cs @@ -0,0 +1,11 @@ +namespace KeepTrack.MovieComponent.Domain +{ + public class MovieModel + { + public string Id { get; set; } + + public string OwnerId { get; set; } + + public string Title { get; set; } + } +} diff --git a/dotnet/src/MovieComponent.Infrastructure.MongoDb/DependencyInjection/ServiceCollectionExtensions.cs b/dotnet/src/MovieComponent.Infrastructure.MongoDb/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..37dcefac --- /dev/null +++ b/dotnet/src/MovieComponent.Infrastructure.MongoDb/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace KeepTrack.MovieComponent.Infrastructure.MongoDb.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMovieInfrastructureMongoDb(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.TryAddTransient(); + + return services; + } + } +} diff --git a/dotnet/src/MovieComponent.Infrastructure.MongoDb/Entities/Movie.cs b/dotnet/src/MovieComponent.Infrastructure.MongoDb/Entities/Movie.cs new file mode 100644 index 00000000..e62b7025 --- /dev/null +++ b/dotnet/src/MovieComponent.Infrastructure.MongoDb/Entities/Movie.cs @@ -0,0 +1,18 @@ +using KeepTrack.Dal.MongoDb.Entities; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace KeepTrack.MovieComponent.Infrastructure.MongoDb.Entities +{ + public class Movie : IEntity + { + [BsonId] + public ObjectId Id { get; set; } + + [BsonElement("owner_id")] + public string OwnerId { get; set; } + + [BsonElement("title")] + public string Title { get; set; } + } +} diff --git a/dotnet/src/MovieComponent.Infrastructure.MongoDb/MappingProfiles/MovieMappingProfile.cs b/dotnet/src/MovieComponent.Infrastructure.MongoDb/MappingProfiles/MovieMappingProfile.cs new file mode 100644 index 00000000..cf0a82a7 --- /dev/null +++ b/dotnet/src/MovieComponent.Infrastructure.MongoDb/MappingProfiles/MovieMappingProfile.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace KeepTrack.MovieComponent.Infrastructure.MongoDb.MappingProfiles +{ + public class MovieMappingProfile : Profile + { + public override string ProfileName + { + get { return "KeepTrackMovieInfrastructureMongoDbMappingProfile"; } + } + + public MovieMappingProfile() + { + CreateMap(); + CreateMap(); + } + } +} diff --git a/dotnet/src/MovieComponent.Infrastructure.MongoDb/MovieComponent.Infrastructure.MongoDb.csproj b/dotnet/src/MovieComponent.Infrastructure.MongoDb/MovieComponent.Infrastructure.MongoDb.csproj new file mode 100644 index 00000000..0a144e55 --- /dev/null +++ b/dotnet/src/MovieComponent.Infrastructure.MongoDb/MovieComponent.Infrastructure.MongoDb.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.1 + KeepTrack.MovieComponent.Infrastructure.MongoDb + KeepTrack.MovieComponent.Infrastructure.MongoDb + {02479358-D65A-4EF1-BA14-BD21706FBA20} + + + + full + true + + + + + + + + diff --git a/dotnet/src/MovieComponent.Infrastructure.MongoDb/Repositories/MovieRepository.cs b/dotnet/src/MovieComponent.Infrastructure.MongoDb/Repositories/MovieRepository.cs new file mode 100644 index 00000000..5bb360b1 --- /dev/null +++ b/dotnet/src/MovieComponent.Infrastructure.MongoDb/Repositories/MovieRepository.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using AutoMapper; +using KeepTrack.Dal.MongoDb.Repositories; +using KeepTrack.MovieComponent.Domain; +using KeepTrack.MovieComponent.Infrastructure.MongoDb.Entities; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Withywoods.Dal.MongoDb; + +namespace KeepTrack.MovieComponent.Infrastructure.MongoDb.Repositories +{ + public class MovieRepository : RepositoryBase, IMovieRepository + { + public MovieRepository(IMongoDbContext mongoDbContext, ILogger logger, IMapper mapper) + : base(mongoDbContext, logger, mapper) + { + } + + protected override string CollectionName => "movie"; + + public async Task> FindAllAsync(string ownerId) + { + var collection = GetCollection(); + var dbEntries = await collection.FindAsync(x => x.OwnerId == ownerId); + return Mapper.Map>(dbEntries.ToList()); + } + } +} From eec2ad64855dacc34c46ad8f140792a3ddfca8a9 Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Thu, 24 Oct 2019 16:52:50 +0200 Subject: [PATCH 5/6] Delete .csproj.user and add it in gitignore --- dotnet/.gitignore | 1 + dotnet/src/Api/Api.csproj.user | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 dotnet/src/Api/Api.csproj.user diff --git a/dotnet/.gitignore b/dotnet/.gitignore index c2f28145..6a33e58b 100644 --- a/dotnet/.gitignore +++ b/dotnet/.gitignore @@ -3,3 +3,4 @@ Debug/ Release/ obj/ appsettings.Development.json +*.csproj.user diff --git a/dotnet/src/Api/Api.csproj.user b/dotnet/src/Api/Api.csproj.user deleted file mode 100644 index d012339b..00000000 --- a/dotnet/src/Api/Api.csproj.user +++ /dev/null @@ -1,17 +0,0 @@ - - - - ProjectDebugger - - - Api - ApiControllerWithActionsScaffolder - root/Controller - 600 - True - False - True - - False - - \ No newline at end of file From baa32f5b700eed2a4749ed482f2fbfe1219bca81 Mon Sep 17 00:00:00 2001 From: Bertrand THOMAS Date: Thu, 24 Oct 2019 17:08:36 +0200 Subject: [PATCH 6/6] Fix SonarCloud issues --- ...tionFilter.cs => CustomExceptionFilterAttribute.cs} | 6 +++--- dotnet/src/Api/Startup.cs | 2 +- .../MappingProfiles/CarMappingProfile.cs | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) rename dotnet/src/Api/Filters/{ExceptionFilter.cs => CustomExceptionFilterAttribute.cs} (85%) diff --git a/dotnet/src/Api/Filters/ExceptionFilter.cs b/dotnet/src/Api/Filters/CustomExceptionFilterAttribute.cs similarity index 85% rename from dotnet/src/Api/Filters/ExceptionFilter.cs rename to dotnet/src/Api/Filters/CustomExceptionFilterAttribute.cs index 34e33056..6bfe61a2 100644 --- a/dotnet/src/Api/Filters/ExceptionFilter.cs +++ b/dotnet/src/Api/Filters/CustomExceptionFilterAttribute.cs @@ -7,12 +7,12 @@ namespace KeepTrack.Api.Filters /// /// Exception filter to make sure the /// - public sealed class ExceptionFilter : ExceptionFilterAttribute + public sealed class CustomExceptionFilterAttribute : ExceptionFilterAttribute { /// - /// Create a new instance of . + /// Create a new instance of . /// - public ExceptionFilter() + public CustomExceptionFilterAttribute() { } diff --git a/dotnet/src/Api/Startup.cs b/dotnet/src/Api/Startup.cs index 4cdccaa2..c8f01ac1 100644 --- a/dotnet/src/Api/Startup.cs +++ b/dotnet/src/Api/Startup.cs @@ -51,7 +51,7 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(opts => { - opts.Filters.Add(); + opts.Filters.Add(); }); ConfigureSwagger(services, _configuration.OpenApiInfo); diff --git a/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs b/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs index 3d1273c2..5d4f96b6 100644 --- a/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs +++ b/dotnet/src/CarComponent.Infrastructure.MongoDb/MappingProfiles/CarMappingProfile.cs @@ -15,6 +15,12 @@ public CarMappingProfile() CreateMap(); CreateMap(); + MapCarHistoryModel(); + MapCarHistory(); + } + + private void MapCarHistoryModel() + { CreateMap() .ForMember(x => x.City, opt => opt.MapFrom(x => x.Location != null ? x.Location.City : null)) .ForMember(x => x.Longitude, opt => opt.MapFrom(x => x.Coordinates != null ? x.Coordinates[0] : (double?)null)) @@ -23,6 +29,10 @@ public CarMappingProfile() .ForMember(x => x.IsFullTank, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.IsFullTank : null)) .ForMember(x => x.DeltaMileage, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.DeltaMileage : null)) .ForMember(x => x.LastRefuelHistoryId, opt => opt.MapFrom(x => x.Fuel != null ? x.Fuel.LastRefuelHistoryId : null)); + } + + private void MapCarHistory() + { CreateMap() .ForMember(x => x.Location, opt => opt.MapFrom(x => x)) .ForMember(x => x.Coordinates, opt => opt.MapFrom(x => (x.Longitude.HasValue && x.Latitude.HasValue) ? new List { x.Longitude.Value, x.Latitude.Value } : null))