# Conflicts: # eShopOnContainers-ServicesAndWebApps.slnpull/223/head
@ -0,0 +1,8 @@ | |||
namespace eShopOnContainers.Core.Models.Location | |||
{ | |||
public class LocationRequest | |||
{ | |||
public double Longitude { get; set; } | |||
public double Latitude { get; set; } | |||
} | |||
} |
@ -0,0 +1,10 @@ | |||
namespace eShopOnContainers.Core.Services.Location | |||
{ | |||
using System.Threading.Tasks; | |||
using eShopOnContainers.Core.Models.Location; | |||
public interface ILocationService | |||
{ | |||
Task UpdateUserLocation(LocationRequest newLocReq); | |||
} | |||
} |
@ -0,0 +1,28 @@ | |||
namespace eShopOnContainers.Core.Services.Location | |||
{ | |||
using eShopOnContainers.Core.Models.Location; | |||
using eShopOnContainers.Core.Services.RequestProvider; | |||
using System; | |||
using System.Threading.Tasks; | |||
public class LocationService : ILocationService | |||
{ | |||
private readonly IRequestProvider _requestProvider; | |||
public LocationService(IRequestProvider requestProvider) | |||
{ | |||
_requestProvider = requestProvider; | |||
} | |||
public async Task UpdateUserLocation(LocationRequest newLocReq) | |||
{ | |||
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.LocationEndpoint); | |||
builder.Path = "api/v1/locations"; | |||
string uri = builder.ToString(); | |||
var result = await _requestProvider.PostAsync(uri, newLocReq); | |||
} | |||
} | |||
} |
@ -0,0 +1,3 @@ | |||
* | |||
!obj/Docker/publish/* | |||
!obj/Docker/empty/ |
@ -0,0 +1,19 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Mvc; | |||
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Controllers | |||
{ | |||
public class HomeController : Controller | |||
{ | |||
// GET: /<controller>/ | |||
public IActionResult Index() | |||
{ | |||
return new RedirectResult("~/swagger"); | |||
} | |||
} | |||
} |
@ -0,0 +1,62 @@ | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Locations.API.Controllers | |||
{ | |||
[Route("api/v1/[controller]")] | |||
[Authorize] | |||
public class LocationsController : ControllerBase | |||
{ | |||
private readonly ILocationsService _locationsService; | |||
private readonly IIdentityService _identityService; | |||
public LocationsController(ILocationsService locationsService, IIdentityService identityService) | |||
{ | |||
_locationsService = locationsService ?? throw new ArgumentNullException(nameof(locationsService)); | |||
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); | |||
} | |||
//GET api/v1/[controller]/user/1 | |||
[Route("user/{userId:int}")] | |||
[HttpGet] | |||
public async Task<IActionResult> GetUserLocation(int userId) | |||
{ | |||
var userLocation = await _locationsService.GetUserLocation(userId); | |||
return Ok(userLocation); | |||
} | |||
//GET api/v1/[controller]/ | |||
[Route("")] | |||
[HttpGet] | |||
public async Task<IActionResult> GetAllLocations() | |||
{ | |||
var locations = await _locationsService.GetAllLocation(); | |||
return Ok(locations); | |||
} | |||
//GET api/v1/[controller]/1 | |||
[Route("{locationId}")] | |||
[HttpGet] | |||
public async Task<IActionResult> GetLocation(string locationId) | |||
{ | |||
var location = await _locationsService.GetLocation(locationId); | |||
return Ok(location); | |||
} | |||
//POST api/v1/[controller]/ | |||
[Route("")] | |||
[HttpPost] | |||
public async Task<IActionResult> CreateOrUpdateUserLocation([FromBody]LocationRequest newLocReq) | |||
{ | |||
var userId = _identityService.GetUserIdentity(); | |||
var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); | |||
return result ? | |||
(IActionResult)Ok() : | |||
(IActionResult)BadRequest(); | |||
} | |||
} | |||
} |
@ -0,0 +1,6 @@ | |||
FROM microsoft/aspnetcore:1.1 | |||
ARG source | |||
WORKDIR /app | |||
EXPOSE 80 | |||
COPY ${source:-obj/Docker/publish} . | |||
ENTRYPOINT ["dotnet", "Locations.API.dll"] |
@ -0,0 +1,14 @@ | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.ActionResults | |||
{ | |||
public class InternalServerErrorObjectResult : ObjectResult | |||
{ | |||
public InternalServerErrorObjectResult(object error) | |||
: base(error) | |||
{ | |||
StatusCode = StatusCodes.Status500InternalServerError; | |||
} | |||
} | |||
} |
@ -0,0 +1,21 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions | |||
{ | |||
using System; | |||
/// <summary> | |||
/// Exception type for app exceptions | |||
/// </summary> | |||
public class LocationDomainException : Exception | |||
{ | |||
public LocationDomainException() | |||
{ } | |||
public LocationDomainException(string message) | |||
: base(message) | |||
{ } | |||
public LocationDomainException(string message, Exception innerException) | |||
: base(message, innerException) | |||
{ } | |||
} | |||
} |
@ -0,0 +1,67 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters | |||
{ | |||
using AspNetCore.Mvc; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Mvc.Filters; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.ActionResults; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions; | |||
using Microsoft.Extensions.Logging; | |||
using System.Net; | |||
public class HttpGlobalExceptionFilter : IExceptionFilter | |||
{ | |||
private readonly IHostingEnvironment env; | |||
private readonly ILogger<HttpGlobalExceptionFilter> logger; | |||
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | |||
{ | |||
this.env = env; | |||
this.logger = logger; | |||
} | |||
public void OnException(ExceptionContext context) | |||
{ | |||
logger.LogError(new EventId(context.Exception.HResult), | |||
context.Exception, | |||
context.Exception.Message); | |||
if (context.Exception.GetType() == typeof(LocationDomainException)) | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { context.Exception.Message } | |||
}; | |||
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 | |||
//It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information | |||
context.Result = new BadRequestObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
else | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { "An error occur.Try it again." } | |||
}; | |||
if (env.IsDevelopment()) | |||
{ | |||
json.DeveloperMessage = context.Exception; | |||
} | |||
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 | |||
// It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information | |||
context.Result = new InternalServerErrorObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | |||
} | |||
context.ExceptionHandled = true; | |||
} | |||
private class JsonErrorResponse | |||
{ | |||
public string[] Messages { get; set; } | |||
public object DeveloperMessage { get; set; } | |||
} | |||
} | |||
} |
@ -0,0 +1,34 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.Extensions.Options; | |||
using MongoDB.Driver; | |||
public class LocationsContext | |||
{ | |||
private readonly IMongoDatabase _database = null; | |||
public LocationsContext(IOptions<LocationSettings> settings) | |||
{ | |||
var client = new MongoClient(settings.Value.ConnectionString); | |||
if (client != null) | |||
_database = client.GetDatabase(settings.Value.Database); | |||
} | |||
public IMongoCollection<UserLocation> UserLocation | |||
{ | |||
get | |||
{ | |||
return _database.GetCollection<UserLocation>("UserLocation"); | |||
} | |||
} | |||
public IMongoCollection<Locations> Locations | |||
{ | |||
get | |||
{ | |||
return _database.GetCollection<Locations>("Locations"); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,145 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure | |||
{ | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using MongoDB.Driver; | |||
using MongoDB.Driver.GeoJsonObjectModel; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
public class LocationsContextSeed | |||
{ | |||
private static LocationsContext ctx; | |||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory) | |||
{ | |||
var config = applicationBuilder | |||
.ApplicationServices.GetRequiredService<IOptions<LocationSettings>>(); | |||
ctx = new LocationsContext(config); | |||
if (!ctx.Locations.Database.GetCollection<Locations>(nameof(Locations)).AsQueryable().Any()) | |||
{ | |||
await SetIndexes(); | |||
await SetUSLocations(); | |||
} | |||
} | |||
static async Task SetUSLocations() | |||
{ | |||
var us = new Locations() | |||
{ | |||
Code = "US", | |||
Description = "United States" | |||
}; | |||
us.SetLocation(-101.357386, 41.650455); | |||
us.SetArea(GetUSPoligon()); | |||
await ctx.Locations.InsertOneAsync(us); | |||
await SetWashingtonLocations(us.Id); | |||
} | |||
static async Task SetWashingtonLocations(string parentId) | |||
{ | |||
var wht = new Locations() | |||
{ | |||
Parent_Id = parentId, | |||
Code = "WHT", | |||
Description = "Washington" | |||
}; | |||
wht.SetLocation(-119.542781, 47.223652); | |||
wht.SetArea(GetWashingtonPoligon()); | |||
await ctx.Locations.InsertOneAsync(wht); | |||
await SetSeattleLocations(wht.Id); | |||
await SetRedmondLocations(wht.Id); | |||
} | |||
static async Task SetSeattleLocations(string parentId) | |||
{ | |||
var stl = new Locations() | |||
{ | |||
Parent_Id = parentId, | |||
Code = "SEAT", | |||
Description = "Seattle" | |||
}; | |||
stl.SetArea(GetSeattlePoligon()); | |||
stl.SetLocation(-122.330747, 47.603111); | |||
await ctx.Locations.InsertOneAsync(stl); | |||
} | |||
static async Task SetRedmondLocations(string parentId) | |||
{ | |||
var rdm = new Locations() | |||
{ | |||
Parent_Id = parentId, | |||
Code = "REDM", | |||
Description = "Redmond" | |||
}; | |||
rdm.SetLocation(-122.122887, 47.674961); | |||
rdm.SetArea(GetRedmondPoligon()); | |||
await ctx.Locations.InsertOneAsync(rdm); | |||
} | |||
static async Task SetIndexes() | |||
{ | |||
// Set location indexes | |||
var builder = Builders<Locations>.IndexKeys; | |||
var keys = builder.Geo2DSphere(prop => prop.Location); | |||
await ctx.Locations.Indexes.CreateOneAsync(keys); | |||
} | |||
static List<GeoJson2DGeographicCoordinates> GetUSPoligon() | |||
{ | |||
return new List<GeoJson2DGeographicCoordinates>() | |||
{ | |||
new GeoJson2DGeographicCoordinates(-62.88205, 48.7985), | |||
new GeoJson2DGeographicCoordinates(-129.3132, 48.76513), | |||
new GeoJson2DGeographicCoordinates(-120.9496, 30.12256), | |||
new GeoJson2DGeographicCoordinates(-111.3944, 30.87114), | |||
new GeoJson2DGeographicCoordinates(-78.11975, 24.24979), | |||
new GeoJson2DGeographicCoordinates(-62.88205, 48.7985) | |||
}; | |||
} | |||
static List<GeoJson2DGeographicCoordinates> GetSeattlePoligon() | |||
{ | |||
return new List<GeoJson2DGeographicCoordinates>() | |||
{ | |||
new GeoJson2DGeographicCoordinates(-122.36238,47.82929), | |||
new GeoJson2DGeographicCoordinates(-122.42091,47.6337), | |||
new GeoJson2DGeographicCoordinates(-122.37371,47.45224), | |||
new GeoJson2DGeographicCoordinates(-122.20788,47.50259), | |||
new GeoJson2DGeographicCoordinates(-122.26934,47.73644), | |||
new GeoJson2DGeographicCoordinates(-122.36238,47.82929) | |||
}; | |||
} | |||
static List<GeoJson2DGeographicCoordinates> GetRedmondPoligon() | |||
{ | |||
return new List<GeoJson2DGeographicCoordinates>() | |||
{ | |||
new GeoJson2DGeographicCoordinates(-122.15432, 47.73148), | |||
new GeoJson2DGeographicCoordinates(-122.17673, 47.72559), | |||
new GeoJson2DGeographicCoordinates(-122.16904, 47.67851), | |||
new GeoJson2DGeographicCoordinates(-122.16136, 47.65036), | |||
new GeoJson2DGeographicCoordinates(-122.15604, 47.62746), | |||
new GeoJson2DGeographicCoordinates(-122.01562, 47.63463), | |||
new GeoJson2DGeographicCoordinates(-122.04961, 47.74244), | |||
new GeoJson2DGeographicCoordinates(-122.15432, 47.73148) | |||
}; | |||
} | |||
static List<GeoJson2DGeographicCoordinates> GetWashingtonPoligon() | |||
{ | |||
return new List<GeoJson2DGeographicCoordinates>() | |||
{ | |||
new GeoJson2DGeographicCoordinates(-124.68633, 48.8943), | |||
new GeoJson2DGeographicCoordinates(-124.32962, 45.66613), | |||
new GeoJson2DGeographicCoordinates(-116.73824, 45.93384), | |||
new GeoJson2DGeographicCoordinates(-116.96912, 49.04282), | |||
new GeoJson2DGeographicCoordinates(-124.68633, 48.8943) | |||
}; | |||
} | |||
} | |||
} |
@ -0,0 +1,23 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using ViewModel; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
public interface ILocationsRepository | |||
{ | |||
Task<Locations> GetAsync(string locationId); | |||
Task<List<Locations>> GetLocationListAsync(); | |||
Task<UserLocation> GetUserLocationAsync(int userId); | |||
Task<List<Locations>> GetCurrentUserRegionsListAsync(LocationRequest currentPosition); | |||
Task AddUserLocationAsync(UserLocation location); | |||
Task UpdateUserLocationAsync(UserLocation userLocation); | |||
} | |||
} |
@ -0,0 +1,66 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories | |||
{ | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.Extensions.Options; | |||
using MongoDB.Bson; | |||
using MongoDB.Driver; | |||
using MongoDB.Driver.GeoJsonObjectModel; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using ViewModel; | |||
public class LocationsRepository | |||
: ILocationsRepository | |||
{ | |||
private readonly LocationsContext _context; | |||
public LocationsRepository(IOptions<LocationSettings> settings) | |||
{ | |||
_context = new LocationsContext(settings); | |||
} | |||
public async Task<Locations> GetAsync(string locationId) | |||
{ | |||
var filter = Builders<Locations>.Filter.Eq("Id", ObjectId.Parse(locationId)); | |||
return await _context.Locations | |||
.Find(filter) | |||
.FirstOrDefaultAsync(); | |||
} | |||
public async Task<UserLocation> GetUserLocationAsync(int userId) | |||
{ | |||
var filter = Builders<UserLocation>.Filter.Eq("UserId", userId); | |||
return await _context.UserLocation | |||
.Find(filter) | |||
.FirstOrDefaultAsync(); | |||
} | |||
public async Task<List<Locations>> GetLocationListAsync() | |||
{ | |||
return await _context.Locations.Find(new BsonDocument()).ToListAsync(); | |||
} | |||
public async Task<List<Locations>> GetCurrentUserRegionsListAsync(LocationRequest currentPosition) | |||
{ | |||
var point = GeoJson.Point(GeoJson.Geographic(currentPosition.Longitude, currentPosition.Latitude)); | |||
var orderByDistanceQuery = new FilterDefinitionBuilder<Locations>().Near(x => x.Location, point); | |||
var withinAreaQuery = new FilterDefinitionBuilder<Locations>().GeoIntersects("Polygon", point); | |||
var filter = Builders<Locations>.Filter.And(orderByDistanceQuery, withinAreaQuery); | |||
return await _context.Locations.Find(filter).ToListAsync(); | |||
} | |||
public async Task AddUserLocationAsync(UserLocation location) | |||
{ | |||
await _context.UserLocation.InsertOneAsync(location); | |||
} | |||
public async Task UpdateUserLocationAsync(UserLocation userLocation) | |||
{ | |||
await _context.UserLocation.ReplaceOneAsync( | |||
doc => doc.UserId == userLocation.UserId, | |||
userLocation, | |||
new UpdateOptions { IsUpsert = true }); | |||
} | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | |||
{ | |||
public interface IIdentityService | |||
{ | |||
string GetUserIdentity(); | |||
} | |||
} |
@ -0,0 +1,18 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
public interface ILocationsService | |||
{ | |||
Task<Locations> GetLocation(string locationId); | |||
Task<UserLocation> GetUserLocation(int id); | |||
Task<List<Locations>> GetAllLocation(); | |||
Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest locRequest); | |||
} | |||
} |
@ -0,0 +1,23 @@ | |||
using Microsoft.AspNetCore.Http; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | |||
{ | |||
public class IdentityService : IIdentityService | |||
{ | |||
private IHttpContextAccessor _context; | |||
public IdentityService(IHttpContextAccessor context) | |||
{ | |||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||
} | |||
public string GetUserIdentity() | |||
{ | |||
return _context.HttpContext.User.FindFirst("sub").Value; | |||
} | |||
} | |||
} |
@ -0,0 +1,63 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | |||
{ | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using System; | |||
using System.Threading.Tasks; | |||
using System.Linq; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions; | |||
using System.Collections.Generic; | |||
public class LocationsService : ILocationsService | |||
{ | |||
private ILocationsRepository _locationsRepository; | |||
public LocationsService(ILocationsRepository locationsRepository) | |||
{ | |||
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); | |||
} | |||
public async Task<Locations> GetLocation(string locationId) | |||
{ | |||
return await _locationsRepository.GetAsync(locationId); | |||
} | |||
public async Task<UserLocation> GetUserLocation(int id) | |||
{ | |||
return await _locationsRepository.GetUserLocationAsync(id); | |||
} | |||
public async Task<List<Locations>> GetAllLocation() | |||
{ | |||
return await _locationsRepository.GetLocationListAsync(); | |||
} | |||
public async Task<bool> AddOrUpdateUserLocation(string id, LocationRequest currentPosition) | |||
{ | |||
if (!int.TryParse(id, out int userId)) | |||
{ | |||
throw new ArgumentException("Not valid userId"); | |||
} | |||
// Get the list of ordered regions the user currently is within | |||
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); | |||
if(currentUserAreaLocationList is null) | |||
{ | |||
throw new LocationDomainException("User current area not found"); | |||
} | |||
// If current area found, then update user location | |||
var locationAncestors = new List<string>(); | |||
var userLocation = await _locationsRepository.GetUserLocationAsync(userId); | |||
userLocation = userLocation ?? new UserLocation(); | |||
userLocation.UserId = userId; | |||
userLocation.LocationId = currentUserAreaLocationList[0].Id; | |||
userLocation.UpdateDate = DateTime.UtcNow; | |||
await _locationsRepository.UpdateUserLocationAsync(userLocation); | |||
return true; | |||
} | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API | |||
{ | |||
public class LocationSettings | |||
{ | |||
public string ExternalCatalogBaseUrl { get; set; } | |||
public string EventBusConnection { get; set; } | |||
public string ConnectionString { get; set; } | |||
public string Database { get; set; } | |||
} | |||
} |
@ -0,0 +1,41 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFramework>netcoreapp1.1</TargetFramework> | |||
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> | |||
<RootNamespace>Microsoft.eShopOnContainers.Services.Locations.API</RootNamespace> | |||
<UserSecretsId>aspnet-Locations.API-20161122013619</UserSecretsId> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Folder Include="wwwroot\" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" /> | |||
<PackageReference Include="mongocsharpdriver" Version="2.4.3" /> | |||
<PackageReference Include="MongoDB.Bson" Version="2.4.3" /> | |||
<PackageReference Include="MongoDB.Driver" Version="2.4.3" /> | |||
<PackageReference Include="MongoDB.Driver.Core" Version="2.4.3" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> | |||
</ItemGroup> | |||
</Project> |
@ -0,0 +1,37 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Model | |||
{ | |||
using MongoDB.Bson; | |||
using MongoDB.Driver.GeoJsonObjectModel; | |||
using System.Collections.Generic; | |||
using MongoDB.Bson.Serialization.Attributes; | |||
public class Locations | |||
{ | |||
[BsonRepresentation(BsonType.ObjectId)] | |||
public string Id { get; set; } | |||
public string Code { get; set; } | |||
[BsonRepresentation(BsonType.ObjectId)] | |||
public string Parent_Id { get; set; } | |||
public string Description { get; set; } | |||
public double Latitude { get; set; } | |||
public double Longitude { get; set; } | |||
public GeoJsonPoint<GeoJson2DGeographicCoordinates> Location { get; private set; } | |||
public GeoJsonPolygon<GeoJson2DGeographicCoordinates> Polygon { get; private set; } | |||
public void SetLocation(double lon, double lat) => SetPosition(lon, lat); | |||
public void SetArea(List<GeoJson2DGeographicCoordinates> coordinatesList) => SetPolygon(coordinatesList); | |||
private void SetPosition(double lon, double lat) | |||
{ | |||
Latitude = lat; | |||
Longitude = lon; | |||
Location = new GeoJsonPoint<GeoJson2DGeographicCoordinates>( | |||
new GeoJson2DGeographicCoordinates(lon, lat)); | |||
} | |||
private void SetPolygon(List<GeoJson2DGeographicCoordinates> coordinatesList) | |||
{ | |||
Polygon = new GeoJsonPolygon<GeoJson2DGeographicCoordinates>(new GeoJsonPolygonCoordinates<GeoJson2DGeographicCoordinates>( | |||
new GeoJsonLinearRingCoordinates<GeoJson2DGeographicCoordinates>(coordinatesList))); | |||
} | |||
} | |||
} |
@ -0,0 +1,17 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Model | |||
{ | |||
using MongoDB.Bson; | |||
using MongoDB.Bson.Serialization.Attributes; | |||
using System; | |||
public class UserLocation | |||
{ | |||
[BsonIgnoreIfDefault] | |||
[BsonRepresentation(BsonType.ObjectId)] | |||
public string Id { get; set; } | |||
public int UserId { get; set; } = 0; | |||
[BsonRepresentation(BsonType.ObjectId)] | |||
public string LocationId { get; set; } | |||
public DateTime UpdateDate { get; set; } | |||
} | |||
} |
@ -0,0 +1,25 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API | |||
{ | |||
public class Program | |||
{ | |||
public static void Main(string[] args) | |||
{ | |||
var host = new WebHostBuilder() | |||
.UseKestrel() | |||
.UseContentRoot(Directory.GetCurrentDirectory()) | |||
.UseStartup<Startup>() | |||
.UseApplicationInsights() | |||
.Build(); | |||
host.Run(); | |||
} | |||
} | |||
} |
@ -0,0 +1,29 @@ | |||
{ | |||
"iisSettings": { | |||
"windowsAuthentication": false, | |||
"anonymousAuthentication": true, | |||
"iisExpress": { | |||
"applicationUrl": "http://localhost:3278/", | |||
"sslPort": 0 | |||
} | |||
}, | |||
"profiles": { | |||
"IIS Express": { | |||
"commandName": "IISExpress", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
} | |||
}, | |||
"Locations.API": { | |||
"commandName": "Project", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
}, | |||
"applicationUrl": "http://localhost:3279" | |||
} | |||
} | |||
} |
@ -0,0 +1,113 @@ | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using System.Reflection; | |||
using System; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API | |||
{ | |||
public class Startup | |||
{ | |||
public IConfigurationRoot Configuration { get; } | |||
public Startup(IHostingEnvironment env) | |||
{ | |||
var builder = new ConfigurationBuilder() | |||
.SetBasePath(env.ContentRootPath) | |||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) | |||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); | |||
if (env.IsDevelopment()) | |||
{ | |||
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly); | |||
} | |||
builder.AddEnvironmentVariables(); | |||
Configuration = builder.Build(); | |||
} | |||
// This method gets called by the runtime. Use this method to add services to the container. | |||
public void ConfigureServices(IServiceCollection services) | |||
{ | |||
// Add framework services. | |||
services.AddMvc(options => | |||
{ | |||
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | |||
}).AddControllersAsServices(); | |||
services.Configure<LocationSettings>(Configuration); | |||
// Add framework services. | |||
services.AddSwaggerGen(options => | |||
{ | |||
options.DescribeAllEnumsAsStrings(); | |||
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info | |||
{ | |||
Title = "eShopOnContainers - Location HTTP API", | |||
Version = "v1", | |||
Description = "The Location Microservice HTTP API. This is a Data-Driven/CRUD microservice sample", | |||
TermsOfService = "Terms Of Service" | |||
}); | |||
}); | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder.AllowAnyOrigin() | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
}); | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
services.AddTransient<IIdentityService, IdentityService>(); | |||
services.AddTransient<ILocationsService, LocationsService>(); | |||
services.AddTransient<ILocationsRepository, LocationsRepository>(); | |||
} | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
//Configure logs | |||
loggerFactory.AddConsole(Configuration.GetSection("Logging")); | |||
loggerFactory.AddDebug(); | |||
app.UseCors("CorsPolicy"); | |||
ConfigureAuth(app); | |||
app.UseMvcWithDefaultRoute(); | |||
app.UseSwagger() | |||
.UseSwaggerUI(c => | |||
{ | |||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); | |||
}); | |||
LocationsContextSeed.SeedAsync(app, loggerFactory) | |||
.Wait(); | |||
} | |||
protected virtual void ConfigureAuth(IApplicationBuilder app) | |||
{ | |||
var identityUrl = Configuration.GetValue<string>("IdentityUrl"); | |||
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions | |||
{ | |||
Authority = identityUrl.ToString(), | |||
ApiName = "locations", | |||
RequireHttpsMetadata = false | |||
}); | |||
} | |||
} | |||
} |
@ -0,0 +1,13 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Locations.API.ViewModel | |||
{ | |||
public class LocationRequest | |||
{ | |||
public double Longitude { get; set; } | |||
public double Latitude { get; set; } | |||
} | |||
} |
@ -0,0 +1,10 @@ | |||
{ | |||
"Logging": { | |||
"IncludeScopes": false, | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
} | |||
} |
@ -0,0 +1,13 @@ | |||
{ | |||
"ConnectionString": "mongodb://nosql.data", | |||
"Database": "LocationsDb", | |||
"IdentityUrl": "http://localhost:5105", | |||
"Logging": { | |||
"IncludeScopes": false, | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
} | |||
} |
@ -0,0 +1,41 @@ | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.TestHost; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Text; | |||
namespace IntegrationTests.Services.Locations | |||
{ | |||
public class LocationsScenarioBase | |||
{ | |||
public TestServer CreateServer() | |||
{ | |||
var webHostBuilder = new WebHostBuilder(); | |||
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Locations"); | |||
webHostBuilder.UseStartup<LocationsTestsStartup>(); | |||
return new TestServer(webHostBuilder); | |||
} | |||
public static class Get | |||
{ | |||
public static string Locations = "api/v1/locations"; | |||
public static string LocationBy(string id) | |||
{ | |||
return $"api/v1/locations/{id}"; | |||
} | |||
public static string UserLocationBy(int id) | |||
{ | |||
return $"api/v1/locations/user/{id}"; | |||
} | |||
} | |||
public static class Post | |||
{ | |||
public static string AddNewLocation = "api/v1/locations/"; | |||
} | |||
} | |||
} |
@ -0,0 +1,138 @@ | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | |||
using Location = Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations; | |||
using System.Collections.Generic; | |||
using Newtonsoft.Json; | |||
using System.Net.Http; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Xunit; | |||
namespace IntegrationTests.Services.Locations | |||
{ | |||
public class LocationsScenarios | |||
: LocationsScenarioBase | |||
{ | |||
[Fact] | |||
public async Task Set_new_user_seattle_location_response_ok_status_code() | |||
{ | |||
using (var server = CreateServer()) | |||
{ | |||
var userId = 1234; | |||
var content = new StringContent(BuildLocationsRequest(-122.315752, 47.604610), UTF8Encoding.UTF8, "application/json"); | |||
// Expected result | |||
var expectedLocation = "SEAT"; | |||
// Act | |||
var response = await server.CreateClient() | |||
.PostAsync(Post.AddNewLocation, content); | |||
var userLocationResponse = await server.CreateClient() | |||
.GetAsync(Get.UserLocationBy(userId)); | |||
var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); | |||
var userLocation = JsonConvert.DeserializeObject<UserLocation>(responseBody); | |||
var locationResponse = await server.CreateClient() | |||
.GetAsync(Get.LocationBy(userLocation.LocationId)); | |||
responseBody = await locationResponse.Content.ReadAsStringAsync(); | |||
var location = JsonConvert.DeserializeObject<Location>(responseBody); | |||
// Assert | |||
Assert.Equal(expectedLocation, location.Code); | |||
} | |||
} | |||
[Fact] | |||
public async Task Set_new_user_readmond_location_response_ok_status_code() | |||
{ | |||
using (var server = CreateServer()) | |||
{ | |||
var userId = 1234; | |||
var content = new StringContent(BuildLocationsRequest(-122.119998, 47.690876), UTF8Encoding.UTF8, "application/json"); | |||
// Expected result | |||
var expectedLocation = "REDM"; | |||
// Act | |||
var response = await server.CreateClient() | |||
.PostAsync(Post.AddNewLocation, content); | |||
var userLocationResponse = await server.CreateClient() | |||
.GetAsync(Get.UserLocationBy(userId)); | |||
var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); | |||
var userLocation = JsonConvert.DeserializeObject<UserLocation>(responseBody); | |||
var locationResponse = await server.CreateClient() | |||
.GetAsync(Get.LocationBy(userLocation.LocationId)); | |||
responseBody = await locationResponse.Content.ReadAsStringAsync(); | |||
var location = JsonConvert.DeserializeObject<Location>(responseBody); | |||
// Assert | |||
Assert.Equal(expectedLocation, location.Code); | |||
} | |||
} | |||
[Fact] | |||
public async Task Set_new_user_Washington_location_response_ok_status_code() | |||
{ | |||
using (var server = CreateServer()) | |||
{ | |||
var userId = 1234; | |||
var content = new StringContent(BuildLocationsRequest(-121.040360, 48.091631), UTF8Encoding.UTF8, "application/json"); | |||
// Expected result | |||
var expectedLocation = "WHT"; | |||
// Act | |||
var response = await server.CreateClient() | |||
.PostAsync(Post.AddNewLocation, content); | |||
var userLocationResponse = await server.CreateClient() | |||
.GetAsync(Get.UserLocationBy(userId)); | |||
var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); | |||
var userLocation = JsonConvert.DeserializeObject<UserLocation>(responseBody); | |||
var locationResponse = await server.CreateClient() | |||
.GetAsync(Get.LocationBy(userLocation.LocationId)); | |||
responseBody = await locationResponse.Content.ReadAsStringAsync(); | |||
var location = JsonConvert.DeserializeObject<Location>(responseBody); | |||
// Assert | |||
Assert.Equal(expectedLocation, location.Code); | |||
} | |||
} | |||
[Fact] | |||
public async Task Get_all_locations_response_ok_status_code() | |||
{ | |||
using (var server = CreateServer()) | |||
{ | |||
var response = await server.CreateClient() | |||
.GetAsync(Get.Locations); | |||
var responseBody = await response.Content.ReadAsStringAsync(); | |||
var locations = JsonConvert.DeserializeObject<List<Location>>(responseBody); | |||
// Assert | |||
Assert.NotEmpty(locations); | |||
} | |||
} | |||
string BuildLocationsRequest(double lon, double lat) | |||
{ | |||
var location = new LocationRequest() | |||
{ | |||
Longitude = lon, | |||
Latitude = lat | |||
}; | |||
return JsonConvert.SerializeObject(location); | |||
} | |||
} | |||
} |
@ -0,0 +1,26 @@ | |||
namespace IntegrationTests.Services.Locations | |||
{ | |||
using IntegrationTests.Middleware; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.eShopOnContainers.Services.Locations.API; | |||
public class LocationsTestsStartup : Startup | |||
{ | |||
public LocationsTestsStartup(IHostingEnvironment env) : base(env) | |||
{ | |||
} | |||
protected override void ConfigureAuth(IApplicationBuilder app) | |||
{ | |||
if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) | |||
{ | |||
app.UseMiddleware<AutoAuthorizeMiddleware>(); | |||
} | |||
else | |||
{ | |||
base.ConfigureAuth(app); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,8 @@ | |||
{ | |||
"ConnectionString": "mongodb://localhost:27017", | |||
"Database": "LocationsDb", | |||
"ExternalCatalogBaseUrl": "http://localhost:5101", | |||
"IdentityUrl": "http://localhost:5105", | |||
"isTest": "true", | |||
"EventBusConnection": "localhost" | |||
} |