diff --git a/ParkingLotApi.sln b/ParkingLotApi.sln index 276b612..6a57d42 100644 --- a/ParkingLotApi.sln +++ b/ParkingLotApi.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34221.43 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkingLotApi", "ParkingLotApi\ParkingLotApi.csproj", "{245411D4-F8E0-4BE6-AF4C-B4EB3AC0ABBE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkingLotApi", "ParkingLotApi\ParkingLotApi.csproj", "{245411D4-F8E0-4BE6-AF4C-B4EB3AC0ABBE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkingLotApiTest", "ParkingLotApiTest\ParkingLotApiTest.csproj", "{3C240C7A-4F4C-42B9-91A1-DE41AAB75DAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParkingLotApiTest", "ParkingLotApiTest\ParkingLotApiTest.csproj", "{3C240C7A-4F4C-42B9-91A1-DE41AAB75DAE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/ParkingLotApi/Controllers/ParkingLotsController.cs b/ParkingLotApi/Controllers/ParkingLotsController.cs new file mode 100644 index 0000000..a649994 --- /dev/null +++ b/ParkingLotApi/Controllers/ParkingLotsController.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Mvc; +using ParkingLotApi.Exceptions; +using ParkingLotApi.Models; +using ParkingLotApi.Services; + +namespace ParkingLotApi.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ParkingLotsController : ControllerBase + { + private readonly ParkingLotsService _parkingLotsService; + public ParkingLotsController(ParkingLotsService parkingLotsService) + { + _parkingLotsService = parkingLotsService; + } + + [HttpPost] + public async Task> AddParkingLot([FromBody] ParkingLotDto parkingLotDto) + { + /* try + {*/ + // return Created("", await _parkingLotsService.AddAsync(parkingLotDto)); + return StatusCode(StatusCodes.Status201Created, await _parkingLotsService.AddAsync(parkingLotDto)); + /* } catch (InvalidCapacityException ex) + { + return BadRequest(ex.Message); + }*/ + } + + [HttpDelete("{id}")] + public async Task RemoveParkingLot(string id) + { + var isRemoved = await _parkingLotsService.RemoveAsync(id); + if (isRemoved) + { + return NoContent(); + } + return NotFound(); + } + + [HttpGet] + public async Task>> GetOnePageParkingLots([FromQuery] int pageIndex) + { + return await _parkingLotsService.GetOnePageParkingLots(pageIndex); + } + + [HttpGet("{id}")] + public async Task>> GetParkingLotById(string id) + { + var parkingLot = await _parkingLotsService.GetByIdAsync(id); + if (parkingLot == null) + { + return NotFound(); + } + return Ok(parkingLot); + } + + [HttpPut("{id}")] + public async Task> UpdateCapacityAsync(string id, [FromBody] int capacity) + { + var updatedParkingLot = await _parkingLotsService.UpdateCapacity(id, capacity); + if (updatedParkingLot == null) + { + return NotFound(); + } + return Ok(updatedParkingLot); + } + } +} \ No newline at end of file diff --git a/ParkingLotApi/Controllers/WeatherForecastController.cs b/ParkingLotApi/Controllers/WeatherForecastController.cs deleted file mode 100644 index e0bef6b..0000000 --- a/ParkingLotApi/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace ParkingLotApi.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/ParkingLotApi/Dtos/ParkingLotDto.cs b/ParkingLotApi/Dtos/ParkingLotDto.cs new file mode 100644 index 0000000..d788dcf --- /dev/null +++ b/ParkingLotApi/Dtos/ParkingLotDto.cs @@ -0,0 +1,14 @@ +namespace ParkingLotApi.Models +{ + public class ParkingLotDto + { + public string Name { get; set; } + public int Capacity { get; set; } + public string Location { get; set; } + + internal ParkingLotEntity ToEntity() + { + return new ParkingLotEntity { Name = Name, Capacity = Capacity, Location = Location }; + } + } +} diff --git a/ParkingLotApi/Exceptions/InvalidCapacityException.cs b/ParkingLotApi/Exceptions/InvalidCapacityException.cs new file mode 100644 index 0000000..f2bf25c --- /dev/null +++ b/ParkingLotApi/Exceptions/InvalidCapacityException.cs @@ -0,0 +1,10 @@ +namespace ParkingLotApi.Exceptions +{ + public class InvalidCapacityException : Exception + { + public InvalidCapacityException(string message) : base(message) + { + + } + } +} diff --git a/ParkingLotApi/Filters/InvalidCapacityExceptionFilter.cs b/ParkingLotApi/Filters/InvalidCapacityExceptionFilter.cs new file mode 100644 index 0000000..efb59ae --- /dev/null +++ b/ParkingLotApi/Filters/InvalidCapacityExceptionFilter.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using ParkingLotApi.Exceptions; + +namespace ParkingLotApi.Filters +{ + public class InvalidCapacityExceptionFilter : IActionFilter, IOrderedFilter + { + public int Order => int.MaxValue - 10; + + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Exception is InvalidCapacityException invalidCapacityException) + { + context.Result = new BadRequestResult(); + context.ExceptionHandled = true; + } + } + + public void OnActionExecuting(ActionExecutingContext context) + { + } + } +} diff --git a/ParkingLotApi/Models/ParkingLotEntity.cs b/ParkingLotApi/Models/ParkingLotEntity.cs new file mode 100644 index 0000000..f9a0e35 --- /dev/null +++ b/ParkingLotApi/Models/ParkingLotEntity.cs @@ -0,0 +1,16 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace ParkingLotApi.Models +{ + public class ParkingLotEntity + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } + + public string Name { get; set; } + public int Capacity { get; set; } + public string Location { get; set; } + } +} diff --git a/ParkingLotApi/Models/ParkingLotsDatabaseSettings.cs b/ParkingLotApi/Models/ParkingLotsDatabaseSettings.cs new file mode 100644 index 0000000..edecc1e --- /dev/null +++ b/ParkingLotApi/Models/ParkingLotsDatabaseSettings.cs @@ -0,0 +1,9 @@ +namespace ParkingLotApi.Models +{ + public class ParkingLotsDatabaseSettings + { + public string ConnectionString { get; set; } = null!; + public string DatabaseName { get; set; } = null!; + public string CollectionName { get; set; } = null!; + } +} diff --git a/ParkingLotApi/ParkingLotApi.csproj b/ParkingLotApi/ParkingLotApi.csproj index 6968690..1ebd9a5 100644 --- a/ParkingLotApi/ParkingLotApi.csproj +++ b/ParkingLotApi/ParkingLotApi.csproj @@ -8,6 +8,8 @@ + + diff --git a/ParkingLotApi/Program.cs b/ParkingLotApi/Program.cs index 8264bac..2fb01b7 100644 --- a/ParkingLotApi/Program.cs +++ b/ParkingLotApi/Program.cs @@ -1,12 +1,26 @@ +using ParkingLotApi.Filters; +using ParkingLotApi.Models; +using ParkingLotApi.Repositories; +using ParkingLotApi.Services; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddControllers(); +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.Configure( + builder.Configuration.GetSection("ParkingLotsDatabase") +); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -22,4 +36,9 @@ app.MapControllers(); -app.Run(); \ No newline at end of file +app.Run(); + +public partial class Program +{ + +} \ No newline at end of file diff --git a/ParkingLotApi/Repositories/IParkingLotsRepository.cs b/ParkingLotApi/Repositories/IParkingLotsRepository.cs new file mode 100644 index 0000000..ec7f0fc --- /dev/null +++ b/ParkingLotApi/Repositories/IParkingLotsRepository.cs @@ -0,0 +1,13 @@ +using ParkingLotApi.Models; + +namespace ParkingLotApi.Repositories +{ + public interface IParkingLotsRepository + { + Task CreateParkingLotAsync(ParkingLotEntity parkingLo); + Task DeleteParkingLotAsync(string id); + Task> GetAllAsync(); + Task GetByIdAsync(string id); + Task UpdateCapacity(string id, ParkingLotEntity updatedParkingLot); + } +} diff --git a/ParkingLotApi/Repositories/ParkingLotsRepository.cs b/ParkingLotApi/Repositories/ParkingLotsRepository.cs new file mode 100644 index 0000000..7fe8e4d --- /dev/null +++ b/ParkingLotApi/Repositories/ParkingLotsRepository.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using ParkingLotApi.Models; + +namespace ParkingLotApi.Repositories +{ + + public class ParkingLotsRepository : IParkingLotsRepository + { + private readonly IMongoCollection _parkingLotsCollection; + public ParkingLotsRepository(IOptions parkingLotsDatabaseSettings) + { + var mongoClient = new MongoClient(parkingLotsDatabaseSettings.Value.ConnectionString); + var mongoDatabase = mongoClient.GetDatabase(parkingLotsDatabaseSettings.Value.DatabaseName); + _parkingLotsCollection = mongoDatabase.GetCollection(parkingLotsDatabaseSettings.Value.CollectionName); + + } + + public async Task CreateParkingLotAsync(ParkingLotEntity parkingLot) + { + await _parkingLotsCollection.InsertOneAsync(parkingLot); + return await _parkingLotsCollection.Find(a => a.Name == parkingLot.Name).FirstAsync(); + } + + public async Task DeleteParkingLotAsync(string id) + { + var result = await _parkingLotsCollection.DeleteOneAsync(p => p.Id == id); + return result.DeletedCount > 0; + } + + public async Task> GetAllAsync() + { + return await _parkingLotsCollection.Find(_ => true).ToListAsync(); + } + + public async Task GetByIdAsync(string id) + { + return await _parkingLotsCollection.Find(p => p.Id == id).FirstOrDefaultAsync(); + } + + public async Task UpdateCapacity(string id, ParkingLotEntity updatedParkingLot) + { + await _parkingLotsCollection.ReplaceOneAsync(p => p.Id == id, updatedParkingLot); + return await GetByIdAsync(id); + } + + + } +} diff --git a/ParkingLotApi/Services/ParkingLotsService.cs b/ParkingLotApi/Services/ParkingLotsService.cs new file mode 100644 index 0000000..ce52c72 --- /dev/null +++ b/ParkingLotApi/Services/ParkingLotsService.cs @@ -0,0 +1,63 @@ +using ParkingLotApi.Exceptions; +using ParkingLotApi.Models; +using ParkingLotApi.Repositories; +using System; + +namespace ParkingLotApi.Services +{ + public class ParkingLotsService + { + private readonly IParkingLotsRepository _parkingLotsRepository; + public ParkingLotsService(IParkingLotsRepository parkingLotsRepository) { + _parkingLotsRepository = parkingLotsRepository; + } + public async Task AddAsync(ParkingLotDto parkingLotDto) + { + if (parkingLotDto.Capacity < 10) + { + throw new InvalidCapacityException("The capacity should be no less than 10"); + } + return await _parkingLotsRepository.CreateParkingLotAsync(parkingLotDto.ToEntity()); + } + + public async Task RemoveAsync(string id) + { + return await _parkingLotsRepository.DeleteParkingLotAsync(id); + } + + public async Task> GetOnePageParkingLots(int pageIndex) + { + var allParkingLots = await _parkingLotsRepository.GetAllAsync(); + int pageSize = 15; + int startIdx = (int)((pageIndex - 1) * pageSize); + return allParkingLots.Skip(startIdx).Take((int)pageSize).ToList(); + } + + public async Task GetByIdAsync(string id) + { + return await _parkingLotsRepository.GetByIdAsync(id); + } + + public IParkingLotsRepository Get_parkingLotsRepository() + { + return _parkingLotsRepository; + } + + public async Task UpdateCapacity(string id, int capacity) + { + var parkingLot = await _parkingLotsRepository.GetByIdAsync(id); + if (parkingLot != null) + { + var updatedParkingLot = new ParkingLotEntity() + { + Capacity = capacity, + Id = parkingLot.Id, + Name = parkingLot.Name, + Location = parkingLot.Location + }; + return await _parkingLotsRepository.UpdateCapacity(id, updatedParkingLot); + } + return null; + } + } +} diff --git a/ParkingLotApi/appsettings.json b/ParkingLotApi/appsettings.json index 10f68b8..f359a48 100644 --- a/ParkingLotApi/appsettings.json +++ b/ParkingLotApi/appsettings.json @@ -5,5 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ParkingLotsDataBase": { + "ConnectionString": "mongodb://localhost:27017/", + "DatabaseName": "ParkingLots", + "CollectionName": "ParkingLots" + } } diff --git a/ParkingLotApiTest/ControllerTests/ParkingLotsControllerTest.cs b/ParkingLotApiTest/ControllerTests/ParkingLotsControllerTest.cs new file mode 100644 index 0000000..8303b52 --- /dev/null +++ b/ParkingLotApiTest/ControllerTests/ParkingLotsControllerTest.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.VisualStudio.TestPlatform.TestHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using ParkingLotApiTest; +using System.Net.Http.Json; +using ParkingLotApi.Models; + +namespace ParkingLotApiTest.ControllerTests +{ + public class ParkingLotsControllerTest : TestBase + { + + public ParkingLotsControllerTest (WebApplicationFactory factory) : base(factory) { } + + [Fact] + public async Task Should_return_400_When_weatherForcast() + { + //given & when + HttpClient _httpClient = GetClient(); + ParkingLotDto parkingLotWithLessThan10 = new ParkingLotDto() + { + Name = "ParkingLot1", + Capacity = 1, + Location = "Chengfu LU" + }; + HttpResponseMessage response = await _httpClient.PostAsJsonAsync("/ParkingLots", parkingLotWithLessThan10); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + } +} diff --git a/ParkingLotApiTest/ControllerTests/WeatherForcastControllerTest.cs b/ParkingLotApiTest/ControllerTests/WeatherForcastControllerTest.cs new file mode 100644 index 0000000..daf2c43 --- /dev/null +++ b/ParkingLotApiTest/ControllerTests/WeatherForcastControllerTest.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.VisualStudio.TestPlatform.TestHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using ParkingLotApiTest; + +namespace ParkingLotApiTest.ControllerTests +{ + public class WeatherForcastControllerTest : TestBase + { + + public WeatherForcastControllerTest(WebApplicationFactory factory) : base(factory) + { + } + [Fact] + public async Task Should_return_correctly_When_weatherForcast() + { + //given & when + HttpClient _httpClient = GetClient(); + HttpResponseMessage response = await _httpClient.GetAsync("/WeatherForecast"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } +} diff --git a/ParkingLotApiTest/TestBase.cs b/ParkingLotApiTest/TestBase.cs new file mode 100644 index 0000000..0deb10a --- /dev/null +++ b/ParkingLotApiTest/TestBase.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Testing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ParkingLotApiTest +{ + public class TestBase : IClassFixture> + { + public TestBase(WebApplicationFactory factory) + { + this.Factory = factory; + } + + protected WebApplicationFactory Factory { get; } + + protected HttpClient GetClient() + { + return Factory.CreateClient(); + } + + } + + +} diff --git a/api.http b/api.http new file mode 100644 index 0000000..e7197fa --- /dev/null +++ b/api.http @@ -0,0 +1,66 @@ +# For more info on HTTP files go to https://aka.ms/vs/httpfile + +@hostname=localhost +@port=?? + +# ParkingLot APIS +## POST /api/ParkingLots +## GET /api/ParkingLots +## GET /api/ParkingLots/{name} +## GET /api/ParkingLots?page-size={x}&index={y} +## PUT /api/ParkingLots/{name} +## DELETE /api/ParkingLots/{name} + +## 1. Create ParkingLot +POST http://{{hostname}}:{{port}}/api/ParkingLots +Content-Type: application/json; charset=utf-8 + +{ + "Name": "parkinglot one", + "Capacity" : 10, + "Location" : "Chengfu Road" +} + +## Response +{ + "Id": "fdjsljldbvjsal", + "Name": "parkinglot one", + "Capacity" : 10, + "Location" : "Chengfu Road" +} +### 1.Created, Return the parkinglot created with Status Created ( 201 ) +### 2.Already Existed, Return Status Bad Request ( 400 ) +### 3.Required Message Invalidation, Return Status Bad Request (400) + +## 2. Delete An Empoyee to Specific Company +POST http://{{hostname}}:{{port}}/api/ParkingLosts/{id} +Content-Type: application/json; charset=utf-8 +## Response +### 1.NoContent with the employee deleted successfully (204) +### 2.NotFound as the company_id or employee_id is wrong (404) + +## 3.Get One Page ParkingLots +GET http://{{hostname}}:{{port}}/api/ParkingLots?pageIndex={index} +Content-Type: application/json; charset=utf-8 + +## Response +### 1.Found, Return all ParkingLots Entity Found with Status OK ( 200 ) + +## 4. Get ParkingLot By ParkingLot_ID +GET http://{{hostname}}:{{port}}/api/ParkingLots/{id} +## Response +### 1.Found, Return the ParkingLot Entity Found with Status OK ( 200 ) +### 2.Not Found, Return Status Not Found ( 404 ) + +## 5. PUT Update Capacity of One ParkingLot Given ID +PUT http://{{hostname}}:{{port}}/api/ParkingLots/{id} +Content-Type: application/json; charset=utf-8 + +## Body +{ + "Capacity" : 20 +} + +## Response +## 1.Updated, Return the ParkingLot updated with Status OK ( 200 ) +## 2.Not Found, Return Status Not Found ( 404 )