-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created City and Measurement entities, registered them in DbContext and UnitOfWork. Defined the Admin and User policies. Wrote the city endpoints. Wrote integration tests for the city endpoints happy paths. Wrote unit tests for all other city cases. Updated Readme.md
- Loading branch information
Radu Terec
committed
Jan 8, 2024
1 parent
114d8dd
commit 9272f60
Showing
26 changed files
with
858 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
using Microsoft.AspNetCore.Mvc.ModelBinding; | ||
|
||
namespace Weather.Api.Core.DataTransferObjects; | ||
|
||
public sealed class CityDTO | ||
{ | ||
public int Id { get; init; } | ||
|
||
[BindRequired] | ||
[MinLength(1)] | ||
[MaxLength(127)] | ||
[RegularExpression(@"^[^`~!@#$%^&*_|+=?;:'\""<>./{}\[\]]+$")] | ||
public string Name { get; init; } = string.Empty; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Collections.ObjectModel; | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
namespace Weather.Api.Core.Models; | ||
|
||
public sealed class City : IEntity | ||
{ | ||
|
||
public int Id { get; init; } | ||
|
||
[Required] | ||
[StringLength(255)] | ||
public string Name { get; set; } = string.Empty; | ||
|
||
public ICollection<Measurement> Measurements { get; set; } = new Collection<Measurement>(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
namespace Weather.Api.Core.Models; | ||
|
||
public sealed class Measurement : IEntity | ||
{ | ||
|
||
public int Id { get; init; } | ||
|
||
[Required] | ||
[Range(-273.15, 999999)] | ||
public float Temperature { get; set; } | ||
|
||
[Required] | ||
[ConcurrencyCheck] | ||
public DateTime Timestamp { get; set; } | ||
|
||
[Required] | ||
public User User { get; set; } = null!; | ||
public int UserId { get; set; } | ||
|
||
[Required] | ||
public City City { get; set; } = null!; | ||
public int CityId { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using Weather.Api.Core.Models; | ||
|
||
namespace Weather.Api.Core.Repositories; | ||
|
||
public interface ICityRepository : IRepository<City> | ||
{ | ||
Task<bool> ExistsAsync(string name); | ||
Task<City?> GetWithMeasurements(int id); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
using Weather.Api.Core; | ||
using Weather.Api.Core.DataTransferObjects; | ||
using Weather.Api.Core.Models; | ||
|
||
namespace Weather.Api.Endpoints; | ||
|
||
public static class CityEndpoints | ||
{ | ||
public static RouteGroupBuilder ToCityEndpoints(this RouteGroupBuilder cityItems) | ||
{ | ||
cityItems.MapGet("/{id:int}", GetAsync).WithTags("Public"); | ||
cityItems.MapPost("/", InsertAsync).RequireAuthorization(Constants.AdminAuthPolicy).WithTags("Private"); | ||
cityItems.MapPut("/{id:int}", UpdateAsync).RequireAuthorization(Constants.AdminAuthPolicy).WithTags("Private"); | ||
cityItems.MapDelete("/{id:int}", DeleteAsync).RequireAuthorization(Constants.AdminAuthPolicy).WithTags("Private"); | ||
|
||
return cityItems; | ||
} | ||
|
||
public static async Task<IResult> GetAsync([FromRoute] int id, IUnitOfWork unitOfWork) | ||
{ | ||
var city = await unitOfWork.Cities.GetAsync(id); | ||
if (city == default) | ||
{ | ||
return TypedResults.Problem(detail: "City not found", statusCode: StatusCodes.Status404NotFound); | ||
} | ||
|
||
var cityDto = new CityDTO { Id = city.Id, Name = city.Name }; | ||
return TypedResults.Ok(cityDto); | ||
} | ||
|
||
public static async Task<IResult> InsertAsync([FromBody] CityDTO cityDto, IUnitOfWork unitOfWork) | ||
{ | ||
if (cityDto.Id != default) | ||
{ | ||
return TypedResults.Problem(detail: "Id cannot be set for a new city", statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
bool alreadyExists = await unitOfWork.Cities.ExistsAsync(cityDto.Name); | ||
if (alreadyExists) | ||
{ | ||
return TypedResults.Problem(detail: "This city already exists", statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
var city = new City { Name = cityDto.Name }; | ||
await unitOfWork.Cities.AddAsync(city); | ||
await unitOfWork.CommitAsync(); | ||
|
||
var insertedCityDto = new CityDTO { Id = city.Id, Name = city.Name }; | ||
return TypedResults.Ok(insertedCityDto); | ||
} | ||
|
||
public static async Task<IResult> UpdateAsync([FromRoute] int id, [FromBody] CityDTO cityDto, IUnitOfWork unitOfWork) | ||
{ | ||
var city = await unitOfWork.Cities.GetAsync(id); | ||
if (city == default) | ||
{ | ||
return TypedResults.Problem(detail: "City not found", statusCode: StatusCodes.Status404NotFound); | ||
} | ||
|
||
bool alreadyExists = await unitOfWork.Cities.ExistsAsync(cityDto.Name); | ||
if (alreadyExists) | ||
{ | ||
return TypedResults.Problem(detail: "This city already exists", statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
city.Name = cityDto.Name; | ||
await unitOfWork.CommitAsync(); | ||
|
||
var updatedCityDto = new CityDTO { Id = city.Id, Name = city.Name }; | ||
return TypedResults.Ok(updatedCityDto); | ||
} | ||
|
||
public static async Task<IResult> DeleteAsync([FromRoute] int id, IUnitOfWork unitOfWork) | ||
{ | ||
var city = await unitOfWork.Cities.GetWithMeasurements(id); | ||
if (city == default) | ||
{ | ||
return TypedResults.Problem(detail: "City not found", statusCode: StatusCodes.Status404NotFound); | ||
} | ||
if (city.Measurements != default && city.Measurements.Count != 0) | ||
{ | ||
return TypedResults.Problem(detail: "Cannot delete a city with measurements", statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
unitOfWork.Cities.Remove(city); | ||
await unitOfWork.CommitAsync(); | ||
|
||
return TypedResults.Ok(id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
using System.Collections.ObjectModel; | ||
|
||
using Microsoft.AspNetCore.Identity; | ||
|
||
using Weather.Api.Core; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.