# Conflicts: # eShopOnContainers-ServicesAndWebApps.slnpull/809/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" | |||||
} |