From 83166cde12f7772c132b8fd1e7bc17c5b5d95321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 30 May 2017 15:01:58 +0200 Subject: [PATCH 1/8] #2412-Create Location Microservice --- ...irewall-rules-for-sts-auth-thru-docker.ps1 | 4 +- docker-compose.override.yml | 10 ++ docker-compose.vs.debug.yml | 15 ++ docker-compose.vs.release.yml | 10 ++ docker-compose.yml | 9 +- eShopOnContainers-ServicesAndWebApps.sln | 56 +++++++- .../Catalog/Catalog.API/Catalog.API.csproj | 1 + .../Identity.API/Configuration/Config.cs | 3 +- .../Location/Locations.API/.dockerignore | 3 + .../Controllers/HomeController.cs | 19 +++ .../Controllers/ValuesController.cs | 44 ++++++ .../Location/Locations.API/Dockerfile | 6 + .../Infrastructure/LocationsContext.cs | 55 ++++++++ .../Infrastructure/LocationsContextSeed.cs | 131 ++++++++++++++++++ .../20170529174524_Initial.Designer.cs | 87 ++++++++++++ .../Migrations/20170529174524_Initial.cs | 129 +++++++++++++++++ .../LocationsContextModelSnapshot.cs | 86 ++++++++++++ .../Locations.API/Locations.API.csproj | 39 ++++++ .../Locations.API/Model/FrontierPoints.cs | 11 ++ .../Location/Locations.API/Model/Locations.cs | 62 +++++++++ .../Location/Locations.API/Program.cs | 25 ++++ .../Properties/launchSettings.json | 29 ++++ .../Location/Locations.API/Startup.cs | 115 +++++++++++++++ .../appsettings.Development.json | 10 ++ .../Location/Locations.API/appsettings.json | 12 ++ .../FunctionalTests/FunctionalTests.csproj | 4 + .../IntegrationTests/IntegrationTests.csproj | 4 + test/Services/UnitTest/UnitTest.csproj | 4 + 28 files changed, 978 insertions(+), 5 deletions(-) create mode 100644 src/Services/Location/Locations.API/.dockerignore create mode 100644 src/Services/Location/Locations.API/Controllers/HomeController.cs create mode 100644 src/Services/Location/Locations.API/Controllers/ValuesController.cs create mode 100644 src/Services/Location/Locations.API/Dockerfile create mode 100644 src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs create mode 100644 src/Services/Location/Locations.API/Locations.API.csproj create mode 100644 src/Services/Location/Locations.API/Model/FrontierPoints.cs create mode 100644 src/Services/Location/Locations.API/Model/Locations.cs create mode 100644 src/Services/Location/Locations.API/Program.cs create mode 100644 src/Services/Location/Locations.API/Properties/launchSettings.json create mode 100644 src/Services/Location/Locations.API/Startup.cs create mode 100644 src/Services/Location/Locations.API/appsettings.Development.json create mode 100644 src/Services/Location/Locations.API/appsettings.json diff --git a/cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1 b/cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1 index be63d8a25..865f24067 100644 --- a/cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1 +++ b/cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1 @@ -21,6 +21,6 @@ try { Write-Host "Rule found" } catch [Exception] { - New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound - New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound + New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound + New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound } \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 04d1c4d9c..a96720ba5 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -95,3 +95,13 @@ services: - spa=http://webspa/hc ports: - "5107:80" + + locations.api: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - EventBusConnection=rabbitmq + ports: + - "5109:80" \ No newline at end of file diff --git a/docker-compose.vs.debug.yml b/docker-compose.vs.debug.yml index 2e7145637..50ba68a69 100644 --- a/docker-compose.vs.debug.yml +++ b/docker-compose.vs.debug.yml @@ -105,3 +105,18 @@ services: entrypoint: tail -f /dev/null labels: - "com.microsoft.visualstudio.targetoperatingsystem=linux" + + locations.api: + image: eshop/locations.api:dev + build: + args: + source: ${DOCKER_BUILD_SOURCE} + environment: + - DOTNET_USE_POLLING_FILE_WATCHER=1 + volumes: + - ./src/Services/Location/Locations.API:/app + - ~/.nuget/packages:/root/.nuget/packages:ro + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" diff --git a/docker-compose.vs.release.yml b/docker-compose.vs.release.yml index d1ca5b2c6..58c9ffb81 100644 --- a/docker-compose.vs.release.yml +++ b/docker-compose.vs.release.yml @@ -70,3 +70,13 @@ services: entrypoint: tail -f /dev/null labels: - "com.microsoft.visualstudio.targetoperatingsystem=linux" + + locations.api: + build: + args: + source: ${DOCKER_BUILD_SOURCE} + volumes: + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" diff --git a/docker-compose.yml b/docker-compose.yml index 21f3972f2..e698992b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,4 +74,11 @@ services: build: context: ./src/Web/WebStatus dockerfile: Dockerfile - \ No newline at end of file + + locations.api: + image: locations.api + build: + context: ./src/Services/Location/Locations.API + dockerfile: Dockerfile + depends_on: + - sql.data \ No newline at end of file diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index eb0e83e02..699185133 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.3 +VisualStudioVersion = 15.0.26430.6 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -76,6 +76,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Health EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locations.API", "src\Services\Location\Locations.API\Locations.API.csproj", "{E7581357-FC34-474C-B8F5-307EE3CE05EF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1002,6 +1006,54 @@ Global {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.Build.0 = Release|Any CPU {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.ActiveCfg = Release|Any CPU {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|ARM.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhone.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x64.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.AppStore|x86.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|ARM.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhone.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x64.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Debug|x86.Build.0 = Debug|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|Any CPU.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|ARM.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhone.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x64.Build.0 = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.ActiveCfg = Release|Any CPU + {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1038,5 +1090,7 @@ Global {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} {89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} + {41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} + {E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6} EndGlobalSection EndGlobal diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 10fe25945..5bea2f22d 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index 260989da4..a07f5d01d 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -12,7 +12,8 @@ namespace Identity.API.Configuration return new List { new ApiResource("orders", "Orders Service"), - new ApiResource("basket", "Basket Service") + new ApiResource("basket", "Basket Service"), + new ApiResource("locations", "Locations Service") }; } diff --git a/src/Services/Location/Locations.API/.dockerignore b/src/Services/Location/Locations.API/.dockerignore new file mode 100644 index 000000000..d8f8175f6 --- /dev/null +++ b/src/Services/Location/Locations.API/.dockerignore @@ -0,0 +1,3 @@ +* +!obj/Docker/publish/* +!obj/Docker/empty/ diff --git a/src/Services/Location/Locations.API/Controllers/HomeController.cs b/src/Services/Location/Locations.API/Controllers/HomeController.cs new file mode 100644 index 000000000..e7cea3cca --- /dev/null +++ b/src/Services/Location/Locations.API/Controllers/HomeController.cs @@ -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: // + public IActionResult Index() + { + return new RedirectResult("~/swagger"); + } + } +} diff --git a/src/Services/Location/Locations.API/Controllers/ValuesController.cs b/src/Services/Location/Locations.API/Controllers/ValuesController.cs new file mode 100644 index 000000000..e711e221d --- /dev/null +++ b/src/Services/Location/Locations.API/Controllers/ValuesController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Locations.API.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/src/Services/Location/Locations.API/Dockerfile b/src/Services/Location/Locations.API/Dockerfile new file mode 100644 index 000000000..3d00f01a9 --- /dev/null +++ b/src/Services/Location/Locations.API/Dockerfile @@ -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"] diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs new file mode 100644 index 000000000..42c3f7900 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs @@ -0,0 +1,55 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure +{ + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + + public class LocationsContext : DbContext + { + public LocationsContext(DbContextOptions options) : base(options) + { + } + + public DbSet Locations { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity(ConfigureLocations); + builder.Entity(ConfigureFrontierPoints); + } + + void ConfigureLocations(EntityTypeBuilder builder) + { + builder.ToTable("Locations"); + + builder.HasKey(cl => cl.Id); + + builder.Property(cl => cl.Id) + .ForSqlServerUseSequenceHiLo("locations_hilo") + .IsRequired(); + + builder.Property(cb => cb.Code) + .IsRequired() + .HasColumnName("LocationCode") + .HasMaxLength(15); + + builder.HasMany(f => f.Polygon) + .WithOne(l => l.Location) + .IsRequired(); + + builder.Property(cb => cb.Description) + .HasMaxLength(100); + } + + void ConfigureFrontierPoints(EntityTypeBuilder builder) + { + builder.ToTable("FrontierPoints"); + + builder.HasKey(fp => fp.Id); + + builder.Property(fp => fp.Id) + .ForSqlServerUseSequenceHiLo("frontier_hilo") + .IsRequired(); + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs new file mode 100644 index 000000000..67673719b --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs @@ -0,0 +1,131 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure +{ + using Microsoft.AspNetCore.Builder; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using System.Text; + + public class LocationsContextSeed + { + public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory) + { + var context = (LocationsContext)applicationBuilder + .ApplicationServices.GetService(typeof(LocationsContext)); + + context.Database.Migrate(); + + if (!context.Locations.Any()) + { + context.Locations.AddRange( + GetPreconfiguredLocations()); + + await context.SaveChangesAsync(); + } + } + + static Locations GetPreconfiguredLocations() + { + var ww = new Locations() { Code = "WW", Description = "WorldWide", Latitude = -1, Longitude = -1 }; + ww.ChildLocations.Add(GetUSLocations()); + return ww; + } + + static Locations GetUSLocations() + { + var us = new Locations() { Code = "US", Description = "United States", Latitude = 41.650455, Longitude = -101.357386, Polygon = GetUSPoligon() }; + us.ChildLocations.Add(GetWashingtonLocations()); + return us; + } + + static Locations GetWashingtonLocations() + { + var wht = new Locations() { Code = "WHT", Description = "Washington", Latitude = 47.223652, Longitude = -119.542781, Polygon = GetWashingtonPoligon() }; + wht.ChildLocations.Add(GetSeattleLocations()); + wht.ChildLocations.Add(GetRedmondLocations()); + return wht; + } + + static Locations GetSeattleLocations() + { + var bcn = new Locations() { Code = "SEAT", Description = "Seattle", Latitude = 47.603111, Longitude = -122.330747, Polygon = GetSeattlePoligon() }; + bcn.ChildLocations.Add(new Locations() { Code = "SEAT-PioneerSqr", Description = "Seattle Pioneer Square Shop" , Latitude = 47.602053, Longitude= -122.333884, Polygon = GetSeattlePioneerSqrPoligon() }); + return bcn; + } + + static Locations GetRedmondLocations() + { + var bcn = new Locations() { Code = "REDM", Description = "Redmond", Latitude = 47.674961, Longitude = -122.122887, Polygon = GetRedmondPoligon() }; + bcn.ChildLocations.Add(new Locations() { Code = "REDM-DWNTWP", Description = "Redmond Downtown Central Park Shop", Latitude = 47.674433, Longitude = -122.125006, Polygon = GetRedmondDowntownParkPoligon() }); + return bcn; + } + + static List GetUSPoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 48.7985, Longitude = -62.88205 }); + poligon.Add(new FrontierPoints() { Latitude = 48.76513, Longitude = -129.31329 }); + poligon.Add(new FrontierPoints() { Latitude = 30.12256, Longitude = -120.9496 }); + poligon.Add(new FrontierPoints() { Latitude = 30.87114, Longitude = -111.39442 }); + poligon.Add(new FrontierPoints() { Latitude = 24.24979, Longitude = -78.11975 }); + return poligon; + } + + static List GetWashingtonPoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 48.8943, Longitude = -124.68633 }); + poligon.Add(new FrontierPoints() { Latitude = 45.66613, Longitude = -124.32962 }); + poligon.Add(new FrontierPoints() { Latitude = 45.93384, Longitude = -116.73824 }); + poligon.Add(new FrontierPoints() { Latitude = 49.04282, Longitude = -116.96912 }); + return poligon; + } + + static List GetSeattlePoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 47.82929, Longitude = -122.36238 }); + poligon.Add(new FrontierPoints() { Latitude = 47.6337, Longitude = -122.42091 }); + poligon.Add(new FrontierPoints() { Latitude = 47.45224, Longitude = -122.37371 }); + poligon.Add(new FrontierPoints() { Latitude = 47.50259, Longitude = -122.20788 }); + poligon.Add(new FrontierPoints() { Latitude = 47.73644, Longitude = -122.26934 }); + return poligon; + } + + static List GetRedmondPoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 47.73148, Longitude = -122.15432 }); + poligon.Add(new FrontierPoints() { Latitude = 47.72559, Longitude = -122.17673 }); + poligon.Add(new FrontierPoints() { Latitude = 47.67851, Longitude = -122.16904 }); + poligon.Add(new FrontierPoints() { Latitude = 47.65036, Longitude = -122.16136 }); + poligon.Add(new FrontierPoints() { Latitude = 47.62746, Longitude = -122.15604 }); + poligon.Add(new FrontierPoints() { Latitude = 47.63463, Longitude = -122.01562 }); + poligon.Add(new FrontierPoints() { Latitude = 47.74244, Longitude = -122.04961 }); + return poligon; + } + + static List GetSeattlePioneerSqrPoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 47.60338, Longitude = -122.3327 }); + poligon.Add(new FrontierPoints() { Latitude = 47.60192, Longitude = -122.33665 }); + poligon.Add(new FrontierPoints() { Latitude = 47.60096, Longitude = -122.33456 }); + poligon.Add(new FrontierPoints() { Latitude = 47.60136, Longitude = -122.33186 }); + return poligon; + } + + static List GetRedmondDowntownParkPoligon() + { + var poligon = new List(); + poligon.Add(new FrontierPoints() { Latitude = 47.67595, Longitude = -122.12467 }); + poligon.Add(new FrontierPoints() { Latitude = 47.67449, Longitude = -122.12862 }); + poligon.Add(new FrontierPoints() { Latitude = 47.67353, Longitude = -122.12653 }); + poligon.Add(new FrontierPoints() { Latitude = 47.67368, Longitude = -122.12197 }); + return poligon; + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs new file mode 100644 index 000000000..0623edc4e --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs @@ -0,0 +1,87 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations +{ + [DbContext(typeof(LocationsContext))] + [Migration("20170529174524_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:Sequence:.frontier_hilo", "'frontier_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.locations_hilo", "'locations_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Latitude"); + + b.Property("LocationId") + .IsRequired(); + + b.Property("Longitude"); + + b.HasKey("Id"); + + b.HasIndex("LocationId"); + + b.ToTable("FrontierPoints"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "locations_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Code") + .IsRequired() + .HasColumnName("LocationCode") + .HasMaxLength(15); + + b.Property("Description") + .HasMaxLength(100); + + b.Property("Latitude"); + + b.Property("Longitude"); + + b.Property("ParentId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") + .WithMany("FrontierPoints") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations") + .WithMany("ChildLocations") + .HasForeignKey("ParentId"); + }); + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs new file mode 100644 index 000000000..d2352312f --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "frontier_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "locations_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Locations", + columns: table => new + { + Id = table.Column(nullable: false), + LocationCode = table.Column(maxLength: 15, nullable: false), + Description = table.Column(maxLength: 100, nullable: true), + Latitude = table.Column(nullable: false), + Longitude = table.Column(nullable: false), + ParentId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Locations", x => x.Id); + table.ForeignKey( + name: "FK_Locations_Locations_ParentId", + column: x => x.ParentId, + principalTable: "Locations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "FrontierPoints", + columns: table => new + { + Id = table.Column(nullable: false), + Latitude = table.Column(nullable: false), + LocationId = table.Column(nullable: false), + Longitude = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FrontierPoints", x => x.Id); + table.ForeignKey( + name: "FK_FrontierPoints_Locations_LocationId", + column: x => x.LocationId, + principalTable: "Locations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_FrontierPoints_LocationId", + table: "FrontierPoints", + column: "LocationId"); + + migrationBuilder.CreateIndex( + name: "IX_Locations_ParentId", + table: "Locations", + column: "ParentId"); + + migrationBuilder.Sql(CreateGetDistanceFunction()); + migrationBuilder.Sql(CreateLocationsNearSP()); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FrontierPoints"); + + migrationBuilder.DropTable( + name: "Locations"); + + migrationBuilder.DropSequence( + name: "frontier_hilo"); + + migrationBuilder.DropSequence( + name: "locations_hilo"); + + migrationBuilder.Sql(@"DROP PROCEDURE IF EXISTS pLocationsNear"); + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS dbo.GetDistanceFromLocation"); + } + + private string CreateLocationsNearSP() + { + var sb = new StringBuilder(); + sb.AppendLine(@"CREATE PROCEDURE [dbo].[pLocationsNear]"); + sb.AppendLine(@"@latitude float,"); + sb.AppendLine(@"@longitude float,"); + sb.AppendLine(@"@size int = 500"); + sb.AppendLine(@"AS"); + sb.AppendLine(@"BEGIN"); + sb.AppendLine(@"SELECT TOP( @size) location.*"); + sb.AppendLine(@"FROM [dbo].[Locations] AS location"); + sb.AppendLine(@"ORDER BY dbo.[GetDistanceFromLocation](location.Latitude, location.Longitude, @latitude, @longitude)"); + sb.AppendLine(@"END"); + return sb.ToString(); + } + + private string CreateGetDistanceFunction() + { + var sb = new StringBuilder(); + sb.AppendLine(@"CREATE FUNCTION [dbo].[GetDistanceFromLocation]("); + sb.AppendLine(@"@CurrentLatitude float,"); + sb.AppendLine(@"@CurrentLongitude float,"); + sb.AppendLine(@"@latitude float,"); + sb.AppendLine(@"@longitude float)"); + sb.AppendLine(@"RETURNS int"); + sb.AppendLine(@"AS"); + sb.AppendLine(@"BEGIN"); + sb.AppendLine(@"DECLARE @geo1 geography = geography::Point(@CurrentLatitude, @CurrentLongitude, 4268),@geo2 geography = geography::Point(@latitude, @longitude, 4268)"); + sb.AppendLine(@"DECLARE @distance int"); + sb.AppendLine(@"SELECT @distance = @geo1.STDistance(@geo2)"); + sb.AppendLine(@"RETURN @distance"); + sb.AppendLine(@"END"); + return sb.ToString(); + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs new file mode 100644 index 000000000..02f77cdd0 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs @@ -0,0 +1,86 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations +{ + [DbContext(typeof(LocationsContext))] + partial class LocationsContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:Sequence:.frontier_hilo", "'frontier_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.locations_hilo", "'locations_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Latitude"); + + b.Property("LocationId") + .IsRequired(); + + b.Property("Longitude"); + + b.HasKey("Id"); + + b.HasIndex("LocationId"); + + b.ToTable("FrontierPoints"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "locations_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Code") + .IsRequired() + .HasColumnName("LocationCode") + .HasMaxLength(15); + + b.Property("Description") + .HasMaxLength(100); + + b.Property("Latitude"); + + b.Property("Longitude"); + + b.Property("ParentId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") + .WithMany("FrontierPoints") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations") + .WithMany("ChildLocations") + .HasForeignKey("ParentId"); + }); + } + } +} diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj new file mode 100644 index 000000000..b00054921 --- /dev/null +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -0,0 +1,39 @@ + + + + netcoreapp1.1 + ..\..\..\..\docker-compose.dcproj + Microsoft.eShopOnContainers.Services.Locations.API + aspnet-Locations.API-20161122013619 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Location/Locations.API/Model/FrontierPoints.cs b/src/Services/Location/Locations.API/Model/FrontierPoints.cs new file mode 100644 index 000000000..4a9112e5e --- /dev/null +++ b/src/Services/Location/Locations.API/Model/FrontierPoints.cs @@ -0,0 +1,11 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +{ + public class FrontierPoints + { + public int Id { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + + public Locations Location { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs new file mode 100644 index 000000000..ec21ac34f --- /dev/null +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +{ + public class Locations + { + public int Id { get; set; } + public string Code { get; set; } + public int? ParentId { get; set; } + public string Description { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public bool IsPointInPolygon(double lat, double lon) => CheckIsPointInPolygon(lat, lon); + + public Locations() + { + ChildLocations = new List(); + } + + public virtual List Polygon { get; set; } + + [ForeignKey("ParentId")] + public virtual List ChildLocations { get; set; } + + + private bool CheckIsPointInPolygon(double lat, double lon) + { + double minX = Polygon[0].Latitude; + double maxX = Polygon[0].Latitude; + double minY = Polygon[0].Longitude; + double maxY = Polygon[0].Longitude; + for (int i = 1; i < Polygon.Count; i++) + { + FrontierPoints q = Polygon[i]; + minX = Math.Min(q.Latitude, minX); + maxX = Math.Max(q.Latitude, maxX); + minY = Math.Min(q.Longitude, minY); + maxY = Math.Max(q.Longitude, maxY); + } + + if (lat < minX || lat > maxX || lon < minY || lon > maxY) + { + return false; + } + + bool inside = false; + for (int i = 0, j = Polygon.Count - 1; i < Polygon.Count; j = i++) + { + if ((Polygon[i].Longitude > lon) != (Polygon[j].Latitude > lat) && + lat < (Polygon[j].Longitude - Polygon[i].Latitude) * (lon - Polygon[i].Longitude) / (Polygon[j].Longitude - Polygon[i].Longitude) + Polygon[i].Latitude) + { + inside = !inside; + } + } + + return inside; + } + } +} diff --git a/src/Services/Location/Locations.API/Program.cs b/src/Services/Location/Locations.API/Program.cs new file mode 100644 index 000000000..345956401 --- /dev/null +++ b/src/Services/Location/Locations.API/Program.cs @@ -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() + .UseApplicationInsights() + .Build(); + + host.Run(); + } + } +} diff --git a/src/Services/Location/Locations.API/Properties/launchSettings.json b/src/Services/Location/Locations.API/Properties/launchSettings.json new file mode 100644 index 000000000..45b637914 --- /dev/null +++ b/src/Services/Location/Locations.API/Properties/launchSettings.json @@ -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" + } + } +} diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs new file mode 100644 index 000000000..1a42679a6 --- /dev/null +++ b/src/Services/Location/Locations.API/Startup.cs @@ -0,0 +1,115 @@ +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; + +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(); + + services.AddDbContext(options => + { + options.UseSqlServer(Configuration["ConnectionString"], + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); + + // Changing default behavior when client evaluation occurs to throw. + // Default in EF Core would be to log a warning when client evaluation is performed. + options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); + //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval + }); + + // 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()); + }); + } + + // 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("IdentityUrl"); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = identityUrl.ToString(), + ApiName = "locations", + RequireHttpsMetadata = false + }); + } + } +} diff --git a/src/Services/Location/Locations.API/appsettings.Development.json b/src/Services/Location/Locations.API/appsettings.Development.json new file mode 100644 index 000000000..fa8ce71a9 --- /dev/null +++ b/src/Services/Location/Locations.API/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Services/Location/Locations.API/appsettings.json b/src/Services/Location/Locations.API/appsettings.json new file mode 100644 index 000000000..812cce207 --- /dev/null +++ b/src/Services/Location/Locations.API/appsettings.json @@ -0,0 +1,12 @@ +{ + "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word;", + "IdentityUrl": "http://localhost:5105", + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 2b8ee1f44..54a74beda 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -38,4 +38,8 @@ + + + + \ No newline at end of file diff --git a/test/Services/IntegrationTests/IntegrationTests.csproj b/test/Services/IntegrationTests/IntegrationTests.csproj index b71e1d4c4..60911dd4d 100644 --- a/test/Services/IntegrationTests/IntegrationTests.csproj +++ b/test/Services/IntegrationTests/IntegrationTests.csproj @@ -46,4 +46,8 @@ + + + + diff --git a/test/Services/UnitTest/UnitTest.csproj b/test/Services/UnitTest/UnitTest.csproj index 3a80e594b..8955e4a45 100644 --- a/test/Services/UnitTest/UnitTest.csproj +++ b/test/Services/UnitTest/UnitTest.csproj @@ -28,4 +28,8 @@ + + + + From 13cfe4337870575914d043e09117f3d61e7893ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Thu, 1 Jun 2017 20:16:19 +0200 Subject: [PATCH 2/8] Created UserLocationModel Map Position to Locations APi Update current user location --- .../Identity.API/Configuration/Config.cs | 6 +- .../Controllers/LocationsController.cs | 35 +++++++++ .../Controllers/ValuesController.cs | 44 ------------ .../Infrastructure/LocationsContext.cs | 23 +++++- .../Infrastructure/LocationsContextSeed.cs | 72 +++++++++---------- ....cs => 20170601140634_Initial.Designer.cs} | 41 +++++++++-- ...4_Initial.cs => 20170601140634_Initial.cs} | 67 +++++++++++------ .../LocationsContextModelSnapshot.cs | 39 ++++++++-- .../Repositories/ILocationsRepository.cs | 17 +++++ .../Repositories/IRepository.cs | 12 ++++ .../Repositories/IUnitOfWork.cs | 13 ++++ .../Repositories/LocationsRepository.cs | 60 ++++++++++++++++ .../Services/IIdentityService.cs | 12 ++++ .../Services/ILocationsService.cs | 10 +++ .../Services/IdentityService.cs | 23 ++++++ .../Services/LocationsService.cs | 62 ++++++++++++++++ .../Locations.API/Locations.API.csproj | 1 + .../Locations.API/Model/FrontierPoints.cs | 16 ++++- .../Location/Locations.API/Model/Locations.cs | 46 ++++++++++-- .../Locations.API/Model/UserLocation.cs | 24 +++++++ .../Location/Locations.API/Startup.cs | 8 +++ .../ViewModel/LocationRequest.cs | 13 ++++ .../IntegrationTests/IntegrationTests.csproj | 5 ++ .../Locations/LocationsScenarioBase.cs | 36 ++++++++++ .../Services/Locations/LocationsScenarios.cs | 37 ++++++++++ .../Locations/LocationsTestsStartup.cs | 26 +++++++ .../Services/Locations/appsettings.json | 7 ++ 27 files changed, 627 insertions(+), 128 deletions(-) create mode 100644 src/Services/Location/Locations.API/Controllers/LocationsController.cs delete mode 100644 src/Services/Location/Locations.API/Controllers/ValuesController.cs rename src/Services/Location/Locations.API/Infrastructure/Migrations/{20170529174524_Initial.Designer.cs => 20170601140634_Initial.Designer.cs} (66%) rename src/Services/Location/Locations.API/Infrastructure/Migrations/{20170529174524_Initial.cs => 20170601140634_Initial.cs} (72%) create mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Services/IIdentityService.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Services/IdentityService.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs create mode 100644 src/Services/Location/Locations.API/Model/UserLocation.cs create mode 100644 src/Services/Location/Locations.API/ViewModel/LocationRequest.cs create mode 100644 test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs create mode 100644 test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs create mode 100644 test/Services/IntegrationTests/Services/Locations/LocationsTestsStartup.cs create mode 100644 test/Services/IntegrationTests/Services/Locations/appsettings.json diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index a07f5d01d..2b78737a9 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -72,7 +72,8 @@ namespace Identity.API.Configuration IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "orders", - "basket" + "basket", + "locations" }, //Allow requesting refresh tokens for long lived API access AllowOfflineAccess = true @@ -103,7 +104,8 @@ namespace Identity.API.Configuration IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "orders", - "basket" + "basket", + "locations" }, } }; diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs new file mode 100644 index 000000000..dc2940b73 --- /dev/null +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -0,0 +1,35 @@ +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)); + } + + //POST api/v1/[controller]/ + [Route("")] + [HttpPost] + public async Task UpdateUserLocation([FromBody]LocationRequest newLocReq) + { + var userId = _identityService.GetUserIdentity(); + var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); + return result ? + (IActionResult)Ok() : + (IActionResult)BadRequest(); + } + } +} diff --git a/src/Services/Location/Locations.API/Controllers/ValuesController.cs b/src/Services/Location/Locations.API/Controllers/ValuesController.cs deleted file mode 100644 index e711e221d..000000000 --- a/src/Services/Location/Locations.API/Controllers/ValuesController.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -namespace Locations.API.Controllers -{ - [Route("api/[controller]")] - public class ValuesController : Controller - { - // GET api/values - [HttpGet] - public IEnumerable Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public string Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody]string value) - { - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody]string value) - { - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs index 42c3f7900..5767694df 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs @@ -2,20 +2,26 @@ { using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; + using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using System.Threading; + using System.Threading.Tasks; - public class LocationsContext : DbContext + public class LocationsContext : DbContext, IUnitOfWork { public LocationsContext(DbContextOptions options) : base(options) { } public DbSet Locations { get; set; } + public DbSet FrontierPoints { get; set; } + public DbSet UserLocation { get; set; } protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(ConfigureLocations); builder.Entity(ConfigureFrontierPoints); + builder.Entity(ConfigureUserLocation); } void ConfigureLocations(EntityTypeBuilder builder) @@ -25,7 +31,7 @@ builder.HasKey(cl => cl.Id); builder.Property(cl => cl.Id) - .ForSqlServerUseSequenceHiLo("locations_hilo") + .ForSqlServerUseSequenceHiLo("locations_seq") .IsRequired(); builder.Property(cb => cb.Code) @@ -48,8 +54,19 @@ builder.HasKey(fp => fp.Id); builder.Property(fp => fp.Id) - .ForSqlServerUseSequenceHiLo("frontier_hilo") + .ForSqlServerUseSequenceHiLo("frontier_seq") .IsRequired(); + } + + void ConfigureUserLocation(EntityTypeBuilder builder) + { + builder.ToTable("UserLocation"); + + builder.Property(ul => ul.Id) + .ForSqlServerUseSequenceHiLo("UserLocation_seq") + .IsRequired(); + + builder.HasIndex(ul => ul.UserId).IsUnique(); } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs index 67673719b..63404f49d 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs @@ -29,21 +29,21 @@ static Locations GetPreconfiguredLocations() { - var ww = new Locations() { Code = "WW", Description = "WorldWide", Latitude = -1, Longitude = -1 }; + var ww = new Locations("WW", "WorldWide", -1, -1); ww.ChildLocations.Add(GetUSLocations()); return ww; } static Locations GetUSLocations() { - var us = new Locations() { Code = "US", Description = "United States", Latitude = 41.650455, Longitude = -101.357386, Polygon = GetUSPoligon() }; + var us = new Locations("US", "United States", 41.650455, -101.357386, GetUSPoligon()); us.ChildLocations.Add(GetWashingtonLocations()); return us; } static Locations GetWashingtonLocations() { - var wht = new Locations() { Code = "WHT", Description = "Washington", Latitude = 47.223652, Longitude = -119.542781, Polygon = GetWashingtonPoligon() }; + var wht = new Locations("WHT", "Washington", 47.223652, -119.542781, GetWashingtonPoligon()); wht.ChildLocations.Add(GetSeattleLocations()); wht.ChildLocations.Add(GetRedmondLocations()); return wht; @@ -51,80 +51,80 @@ static Locations GetSeattleLocations() { - var bcn = new Locations() { Code = "SEAT", Description = "Seattle", Latitude = 47.603111, Longitude = -122.330747, Polygon = GetSeattlePoligon() }; - bcn.ChildLocations.Add(new Locations() { Code = "SEAT-PioneerSqr", Description = "Seattle Pioneer Square Shop" , Latitude = 47.602053, Longitude= -122.333884, Polygon = GetSeattlePioneerSqrPoligon() }); + var bcn = new Locations("SEAT", "Seattle", 47.603111, -122.330747, GetSeattlePoligon()); + bcn.ChildLocations.Add(new Locations("SEAT-PioneerSqr", "Seattle Pioneer Square Shop", 47.602053, -122.333884, GetSeattlePioneerSqrPoligon())); return bcn; } static Locations GetRedmondLocations() { - var bcn = new Locations() { Code = "REDM", Description = "Redmond", Latitude = 47.674961, Longitude = -122.122887, Polygon = GetRedmondPoligon() }; - bcn.ChildLocations.Add(new Locations() { Code = "REDM-DWNTWP", Description = "Redmond Downtown Central Park Shop", Latitude = 47.674433, Longitude = -122.125006, Polygon = GetRedmondDowntownParkPoligon() }); + var bcn = new Locations("REDM", "Redmond", 47.674961, -122.122887, GetRedmondPoligon()); + bcn.ChildLocations.Add(new Locations("REDM-DWNTWP", "Redmond Downtown Central Park Shop", 47.674433, -122.125006, GetRedmondDowntownParkPoligon())); return bcn; } static List GetUSPoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 48.7985, Longitude = -62.88205 }); - poligon.Add(new FrontierPoints() { Latitude = 48.76513, Longitude = -129.31329 }); - poligon.Add(new FrontierPoints() { Latitude = 30.12256, Longitude = -120.9496 }); - poligon.Add(new FrontierPoints() { Latitude = 30.87114, Longitude = -111.39442 }); - poligon.Add(new FrontierPoints() { Latitude = 24.24979, Longitude = -78.11975 }); + poligon.Add(new FrontierPoints(48.7985, -62.88205)); + poligon.Add(new FrontierPoints(48.76513, -129.3132)); + poligon.Add(new FrontierPoints(30.12256, -120.9496)); + poligon.Add(new FrontierPoints(30.87114, -111.3944)); + poligon.Add(new FrontierPoints(24.24979, -78.11975)); return poligon; } static List GetWashingtonPoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 48.8943, Longitude = -124.68633 }); - poligon.Add(new FrontierPoints() { Latitude = 45.66613, Longitude = -124.32962 }); - poligon.Add(new FrontierPoints() { Latitude = 45.93384, Longitude = -116.73824 }); - poligon.Add(new FrontierPoints() { Latitude = 49.04282, Longitude = -116.96912 }); + poligon.Add(new FrontierPoints(48.8943, -124.68633)); + poligon.Add(new FrontierPoints(45.66613, -124.32962)); + poligon.Add(new FrontierPoints(45.93384, -116.73824)); + poligon.Add(new FrontierPoints(49.04282, -116.96912)); return poligon; } static List GetSeattlePoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 47.82929, Longitude = -122.36238 }); - poligon.Add(new FrontierPoints() { Latitude = 47.6337, Longitude = -122.42091 }); - poligon.Add(new FrontierPoints() { Latitude = 47.45224, Longitude = -122.37371 }); - poligon.Add(new FrontierPoints() { Latitude = 47.50259, Longitude = -122.20788 }); - poligon.Add(new FrontierPoints() { Latitude = 47.73644, Longitude = -122.26934 }); + poligon.Add(new FrontierPoints(47.82929, -122.36238)); + poligon.Add(new FrontierPoints(47.6337, -122.42091)); + poligon.Add(new FrontierPoints(47.45224, -122.37371)); + poligon.Add(new FrontierPoints(47.50259, -122.20788)); + poligon.Add(new FrontierPoints(47.73644, -122.26934)); return poligon; } static List GetRedmondPoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 47.73148, Longitude = -122.15432 }); - poligon.Add(new FrontierPoints() { Latitude = 47.72559, Longitude = -122.17673 }); - poligon.Add(new FrontierPoints() { Latitude = 47.67851, Longitude = -122.16904 }); - poligon.Add(new FrontierPoints() { Latitude = 47.65036, Longitude = -122.16136 }); - poligon.Add(new FrontierPoints() { Latitude = 47.62746, Longitude = -122.15604 }); - poligon.Add(new FrontierPoints() { Latitude = 47.63463, Longitude = -122.01562 }); - poligon.Add(new FrontierPoints() { Latitude = 47.74244, Longitude = -122.04961 }); + poligon.Add(new FrontierPoints(47.73148, -122.15432)); + poligon.Add(new FrontierPoints(47.72559, -122.17673)); + poligon.Add(new FrontierPoints(47.67851, -122.16904)); + poligon.Add(new FrontierPoints(47.65036, -122.16136)); + poligon.Add(new FrontierPoints(47.62746, -122.15604)); + poligon.Add(new FrontierPoints(47.63463, -122.01562)); + poligon.Add(new FrontierPoints(47.74244, -122.04961)); return poligon; } static List GetSeattlePioneerSqrPoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 47.60338, Longitude = -122.3327 }); - poligon.Add(new FrontierPoints() { Latitude = 47.60192, Longitude = -122.33665 }); - poligon.Add(new FrontierPoints() { Latitude = 47.60096, Longitude = -122.33456 }); - poligon.Add(new FrontierPoints() { Latitude = 47.60136, Longitude = -122.33186 }); + poligon.Add(new FrontierPoints(47.60338, -122.3327)); + poligon.Add(new FrontierPoints(47.60192, -122.33665)); + poligon.Add(new FrontierPoints(47.60096, -122.33456)); + poligon.Add(new FrontierPoints(47.60136, -122.33186)); return poligon; } static List GetRedmondDowntownParkPoligon() { var poligon = new List(); - poligon.Add(new FrontierPoints() { Latitude = 47.67595, Longitude = -122.12467 }); - poligon.Add(new FrontierPoints() { Latitude = 47.67449, Longitude = -122.12862 }); - poligon.Add(new FrontierPoints() { Latitude = 47.67353, Longitude = -122.12653 }); - poligon.Add(new FrontierPoints() { Latitude = 47.67368, Longitude = -122.12197 }); + poligon.Add(new FrontierPoints(47.67595, -122.12467)); + poligon.Add(new FrontierPoints(47.67449, -122.12862)); + poligon.Add(new FrontierPoints(47.67353, -122.12653)); + poligon.Add(new FrontierPoints(47.67368, -122.12197)); return poligon; } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs similarity index 66% rename from src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs rename to src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs index 0623edc4e..441ec1d82 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.Designer.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs @@ -8,22 +8,23 @@ using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations { [DbContext(typeof(LocationsContext))] - [Migration("20170529174524_Initial")] + [Migration("20170601140634_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { modelBuilder .HasAnnotation("ProductVersion", "1.1.2") - .HasAnnotation("SqlServer:Sequence:.frontier_hilo", "'frontier_hilo', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.locations_hilo", "'locations_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.frontier_seq", "'frontier_seq', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.locations_seq", "'locations_seq', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.UserLocation_seq", "'UserLocation_seq', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_hilo") + .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); b.Property("Latitude"); @@ -44,7 +45,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr { b.Property("Id") .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "locations_hilo") + .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); b.Property("Code") @@ -68,10 +69,31 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr b.ToTable("Locations"); }); + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("LocationId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("LocationId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserLocation"); + }); + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => { b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany("FrontierPoints") + .WithMany("Polygon") .HasForeignKey("LocationId") .OnDelete(DeleteBehavior.Cascade); }); @@ -82,6 +104,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr .WithMany("ChildLocations") .HasForeignKey("ParentId"); }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") + .WithMany() + .HasForeignKey("LocationId"); + }); } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs similarity index 72% rename from src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs rename to src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs index d2352312f..3073ca8ce 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170529174524_Initial.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs @@ -10,11 +10,15 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateSequence( - name: "frontier_hilo", + name: "frontier_seq", incrementBy: 10); migrationBuilder.CreateSequence( - name: "locations_hilo", + name: "locations_seq", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "UserLocation_seq", incrementBy: 10); migrationBuilder.CreateTable( @@ -59,6 +63,25 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "UserLocation", + columns: table => new + { + Id = table.Column(nullable: false), + LocationId = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserLocation", x => x.Id); + table.ForeignKey( + name: "FK_UserLocation_Locations_LocationId", + column: x => x.LocationId, + principalTable: "Locations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateIndex( name: "IX_FrontierPoints_LocationId", table: "FrontierPoints", @@ -69,8 +92,18 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr table: "Locations", column: "ParentId"); + migrationBuilder.CreateIndex( + name: "IX_UserLocation_LocationId", + table: "UserLocation", + column: "LocationId"); + + migrationBuilder.CreateIndex( + name: "IX_UserLocation_UserId", + table: "UserLocation", + column: "UserId", + unique: true); + migrationBuilder.Sql(CreateGetDistanceFunction()); - migrationBuilder.Sql(CreateLocationsNearSP()); } protected override void Down(MigrationBuilder migrationBuilder) @@ -78,33 +111,22 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr migrationBuilder.DropTable( name: "FrontierPoints"); + migrationBuilder.DropTable( + name: "UserLocation"); + migrationBuilder.DropTable( name: "Locations"); migrationBuilder.DropSequence( - name: "frontier_hilo"); + name: "frontier_seq"); migrationBuilder.DropSequence( - name: "locations_hilo"); + name: "locations_seq"); - migrationBuilder.Sql(@"DROP PROCEDURE IF EXISTS pLocationsNear"); - migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS dbo.GetDistanceFromLocation"); - } + migrationBuilder.DropSequence( + name: "UserLocation_seq"); - private string CreateLocationsNearSP() - { - var sb = new StringBuilder(); - sb.AppendLine(@"CREATE PROCEDURE [dbo].[pLocationsNear]"); - sb.AppendLine(@"@latitude float,"); - sb.AppendLine(@"@longitude float,"); - sb.AppendLine(@"@size int = 500"); - sb.AppendLine(@"AS"); - sb.AppendLine(@"BEGIN"); - sb.AppendLine(@"SELECT TOP( @size) location.*"); - sb.AppendLine(@"FROM [dbo].[Locations] AS location"); - sb.AppendLine(@"ORDER BY dbo.[GetDistanceFromLocation](location.Latitude, location.Longitude, @latitude, @longitude)"); - sb.AppendLine(@"END"); - return sb.ToString(); + migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS dbo.GetDistanceFromLocation"); } private string CreateGetDistanceFunction() @@ -125,5 +147,6 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr sb.AppendLine(@"END"); return sb.ToString(); } + } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs index 02f77cdd0..c01237ab2 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs @@ -14,15 +14,16 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr { modelBuilder .HasAnnotation("ProductVersion", "1.1.2") - .HasAnnotation("SqlServer:Sequence:.frontier_hilo", "'frontier_hilo', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.locations_hilo", "'locations_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.frontier_seq", "'frontier_seq', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.locations_seq", "'locations_seq', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.UserLocation_seq", "'UserLocation_seq', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_hilo") + .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); b.Property("Latitude"); @@ -43,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr { b.Property("Id") .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "locations_hilo") + .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); b.Property("Code") @@ -67,10 +68,31 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr b.ToTable("Locations"); }); + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("LocationId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("LocationId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserLocation"); + }); + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => { b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany("FrontierPoints") + .WithMany("Polygon") .HasForeignKey("LocationId") .OnDelete(DeleteBehavior.Cascade); }); @@ -81,6 +103,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migr .WithMany("ChildLocations") .HasForeignKey("ParentId"); }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") + .WithMany() + .HasForeignKey("LocationId"); + }); } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs new file mode 100644 index 000000000..bd3b58cb2 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs @@ -0,0 +1,17 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories +{ + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface ILocationsRepository : IRepository + { + UserLocation Add(UserLocation order); + + void Update(UserLocation order); + + Task GetAsync(int userId); + + Task> GetNearestLocationListAsync(double lat, double lon); + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs new file mode 100644 index 000000000..946cf489c --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories +{ + public interface IRepository + { + IUnitOfWork UnitOfWork { get; } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs new file mode 100644 index 000000000..a81ac95e1 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories +{ + public interface IUnitOfWork : IDisposable + { + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs new file mode 100644 index 000000000..98cb501b9 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs @@ -0,0 +1,60 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories +{ + using System; + using System.Threading.Tasks; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using Microsoft.EntityFrameworkCore; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + public class LocationsRepository + : ILocationsRepository + { + private readonly LocationsContext _context; + + public IUnitOfWork UnitOfWork + { + get + { + return _context; + } + } + + public LocationsRepository(LocationsContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public UserLocation Add(UserLocation location) + { + return _context.UserLocation.Add(location).Entity; + + } + + public async Task GetAsync(int userId) + { + return await _context.UserLocation + .Where(ul => ul.UserId == userId) + .SingleOrDefaultAsync(); + } + + public async Task> GetNearestLocationListAsync(double lat, double lon) + { + var query = $"SELECT TOP(100) location.* " + + $"FROM[dbo].[Locations] AS location " + + $"ORDER BY [dbo].[GetDistanceFromLocation](location.Latitude, location.Longitude, " + + $"{lat.ToString(CultureInfo.InvariantCulture)}, " + + $"{lon.ToString(CultureInfo.InvariantCulture)})"; + + return await _context.Locations.FromSql(query) + .Include(f => f.Polygon) + .ToListAsync(); + } + + public void Update(UserLocation location) + { + _context.Entry(location).State = EntityState.Modified; + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/IIdentityService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/IIdentityService.cs new file mode 100644 index 000000000..d49abe111 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Services/IIdentityService.cs @@ -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(); + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs new file mode 100644 index 000000000..672dd2703 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -0,0 +1,10 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services +{ + using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; + using System.Threading.Tasks; + + public interface ILocationsService + { + Task AddOrUpdateUserLocation(string userId, LocationRequest locRequest); + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/IdentityService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/IdentityService.cs new file mode 100644 index 000000000..bcb7a84f8 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Services/IdentityService.cs @@ -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; + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs new file mode 100644 index 000000000..d2b721399 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -0,0 +1,62 @@ +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; + + public class LocationsService : ILocationsService + { + private ILocationsRepository _locationsRepository; + + public LocationsService(ILocationsRepository locationsRepository) + { + _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); + } + + public async Task AddOrUpdateUserLocation(string id, LocationRequest currentPosition) + { + int.TryParse(id, out int userId); + var currentUserLocation = await _locationsRepository.GetAsync(userId); + + // Get the nearest locations ordered by proximity + var nearestLocationList = await _locationsRepository.GetNearestLocationListAsync(currentPosition.Latitude, currentPosition.Longitude); + + // Check out in which region we currently are + foreach(var locCandidate in nearestLocationList) + { + // Check location's tree and retrive user most specific area + var findNewLocationResult = locCandidate.GetUserMostSpecificLocation(currentPosition.Latitude, currentPosition.Longitude); + if (findNewLocationResult.isSuccess) + { + CreateUserLocation(currentUserLocation, findNewLocationResult.location, userId); + UpdateUserLocation(currentUserLocation, findNewLocationResult.location); + break; + } + } + + var result = await _locationsRepository.UnitOfWork.SaveChangesAsync(); + return result > 0; + } + + private void CreateUserLocation(UserLocation currentUserLocation, Locations newLocation, int userId) + { + if (currentUserLocation is null) + { + currentUserLocation = currentUserLocation ?? new UserLocation(userId); + currentUserLocation.Location = newLocation; + _locationsRepository.Add(currentUserLocation); + } + } + + private void UpdateUserLocation(UserLocation currentUserLocation, Locations newLocation) + { + if (currentUserLocation != null) + { + currentUserLocation.Location = newLocation; + _locationsRepository.Update(currentUserLocation); + } + } + } +} diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj index b00054921..34808c5e0 100644 --- a/src/Services/Location/Locations.API/Locations.API.csproj +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -7,6 +7,7 @@ aspnet-Locations.API-20161122013619 + diff --git a/src/Services/Location/Locations.API/Model/FrontierPoints.cs b/src/Services/Location/Locations.API/Model/FrontierPoints.cs index 4a9112e5e..e9061a88f 100644 --- a/src/Services/Location/Locations.API/Model/FrontierPoints.cs +++ b/src/Services/Location/Locations.API/Model/FrontierPoints.cs @@ -3,9 +3,19 @@ public class FrontierPoints { public int Id { get; set; } - public double Latitude { get; set; } - public double Longitude { get; set; } + public double Latitude { get; private set; } + public double Longitude { get; private set; } - public Locations Location { get; set; } + public FrontierPoints() + { + } + + public FrontierPoints(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + + public Locations Location { get; private set; } } } \ No newline at end of file diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs index ec21ac34f..71ffb603e 100644 --- a/src/Services/Location/Locations.API/Model/Locations.cs +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -7,27 +7,59 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Model { public class Locations { - public int Id { get; set; } - public string Code { get; set; } - public int? ParentId { get; set; } - public string Description { get; set; } - public double Latitude { get; set; } - public double Longitude { get; set; } - public bool IsPointInPolygon(double lat, double lon) => CheckIsPointInPolygon(lat, lon); + public int Id { get; private set; } + public string Code { get; private set; } + public int? ParentId { get; private set; } + public string Description { get; private set; } + public double Latitude { get; private set; } + public double Longitude { get; private set; } + public (Locations location, bool isSuccess) GetUserMostSpecificLocation(double userLatitude, double userLongitude) + => CheckUserMostSpecificLocation(userLatitude, userLongitude); public Locations() { ChildLocations = new List(); } + public Locations(string code, string description, double latitude, double longitude, List polygon = null) : this() + { + Code = code; + Description = description; + Latitude = latitude; + Longitude = longitude; + Polygon = polygon ?? new List(); + } + public virtual List Polygon { get; set; } [ForeignKey("ParentId")] public virtual List ChildLocations { get; set; } + private (Locations location, bool isSuccess) CheckUserMostSpecificLocation(double userLatitude, double userLongitude, Locations location = null) + { + Locations result = this; + var childLocations = location != null ? location.ChildLocations : ChildLocations; + + // Check if user is in location's area, if not then returns false + if (!CheckIsPointInPolygon(userLatitude, userLongitude)) + { + return (this, false); + } + + foreach (var childLocation in childLocations) + { + result = childLocation; + + if (childLocation.ChildLocations.Count == 0){ break; } + + CheckUserMostSpecificLocation(userLatitude, userLongitude, childLocation); + } + return (result, true); + } private bool CheckIsPointInPolygon(double lat, double lon) { + if(Polygon.Count == 0) { return false; }; double minX = Polygon[0].Latitude; double maxX = Polygon[0].Latitude; double minY = Polygon[0].Longitude; diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs new file mode 100644 index 000000000..f26196ace --- /dev/null +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +{ + public class UserLocation + { + public int Id { get; set; } + public int UserId { get; set; } + + public UserLocation() + { + } + + public UserLocation(int userId) : this() + { + UserId = userId; + } + + public Locations Location { get; set; } + } +} diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index 1a42679a6..c13117b87 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -8,6 +8,9 @@ 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; namespace Microsoft.eShopOnContainers.Services.Locations.API { @@ -75,6 +78,11 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API .AllowAnyHeader() .AllowCredentials()); }); + + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Location/Locations.API/ViewModel/LocationRequest.cs b/src/Services/Location/Locations.API/ViewModel/LocationRequest.cs new file mode 100644 index 000000000..8619bfc0f --- /dev/null +++ b/src/Services/Location/Locations.API/ViewModel/LocationRequest.cs @@ -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; } + } +} diff --git a/test/Services/IntegrationTests/IntegrationTests.csproj b/test/Services/IntegrationTests/IntegrationTests.csproj index 60911dd4d..0306f2005 100644 --- a/test/Services/IntegrationTests/IntegrationTests.csproj +++ b/test/Services/IntegrationTests/IntegrationTests.csproj @@ -13,6 +13,7 @@ + @@ -20,6 +21,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -28,6 +32,7 @@ + diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs new file mode 100644 index 000000000..b9d3af44c --- /dev/null +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs @@ -0,0 +1,36 @@ +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(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string Locations = "api/v1/locations"; + + public static string LocationBy(int id) + { + return $"api/v1/locations/{id}"; + } + } + + public static class Post + { + public static string AddNewLocation = "api/v1/locations/"; + } + } +} diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs new file mode 100644 index 000000000..0c177d6f4 --- /dev/null +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs @@ -0,0 +1,37 @@ +using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; +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_location_response_ok_status_code() + { + using (var server = CreateServer()) + { + var content = new StringContent(BuildLocationsRequest(), UTF8Encoding.UTF8, "application/json"); + + var response = await server.CreateClient() + .PostAsync(Post.AddNewLocation, content); + + response.EnsureSuccessStatusCode(); + } + } + + string BuildLocationsRequest() + { + var location = new LocationRequest() + { + Longitude = -122.333875, + Latitude = 47.602050 + }; + return JsonConvert.SerializeObject(location); + } + } +} diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsTestsStartup.cs b/test/Services/IntegrationTests/Services/Locations/LocationsTestsStartup.cs new file mode 100644 index 000000000..0e622d854 --- /dev/null +++ b/test/Services/IntegrationTests/Services/Locations/LocationsTestsStartup.cs @@ -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(); + } + else + { + base.ConfigureAuth(app); + } + } + } +} diff --git a/test/Services/IntegrationTests/Services/Locations/appsettings.json b/test/Services/IntegrationTests/Services/Locations/appsettings.json new file mode 100644 index 000000000..c31d6c9f1 --- /dev/null +++ b/test/Services/IntegrationTests/Services/Locations/appsettings.json @@ -0,0 +1,7 @@ +{ + "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word;", + "ExternalCatalogBaseUrl": "http://localhost:5101", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +} From 896ab5b4dc4cbf7bc5ceaf22285ad4d2402a1eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Thu, 8 Jun 2017 17:33:55 +0200 Subject: [PATCH 3/8] Adapt Locations.api to nosql db --- docker-compose.override.yml | 7 +- docker-compose.yml | 5 +- .../Controllers/LocationsController.cs | 18 ++ .../InternalServerErrorObjectResult.cs | 14 ++ .../Exceptions/LocationDomainException.cs | 21 ++ .../Filters/HttpGlobalExceptionFilter.cs | 67 +++++++ .../Infrastructure/LocationsContext.cs | 76 ++------ .../Infrastructure/LocationsContextSeed.cs | 182 +++++++++--------- .../20170601140634_Initial.Designer.cs | 116 ----------- .../Migrations/20170601140634_Initial.cs | 152 --------------- .../LocationsContextModelSnapshot.cs | 115 ----------- .../Repositories/ILocationsRepository.cs | 18 +- .../Repositories/IRepository.cs | 12 -- .../Repositories/IUnitOfWork.cs | 13 -- .../Repositories/LocationsRepository.cs | 67 ++++--- .../Services/ILocationsService.cs | 6 + .../Services/LocationsService.cs | 70 ++++--- .../Locations.API/LocationSettings.cs | 15 ++ .../Locations.API/Locations.API.csproj | 7 +- .../Locations.API/Model/FrontierPoints.cs | 21 -- .../Location/Locations.API/Model/Locations.cs | 107 ++-------- .../Locations.API/Model/UserLocation.cs | 30 ++- .../Location/Locations.API/Startup.cs | 24 +-- .../Location/Locations.API/appsettings.json | 3 +- .../Services/Locations/LocationsScenarios.cs | 26 ++- .../Services/Locations/appsettings.json | 3 +- 26 files changed, 414 insertions(+), 781 deletions(-) create mode 100644 src/Services/Location/Locations.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Exceptions/LocationDomainException.cs create mode 100644 src/Services/Location/Locations.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs delete mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs delete mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs delete mode 100644 src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs delete mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs delete mode 100644 src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs create mode 100644 src/Services/Location/Locations.API/LocationSettings.cs delete mode 100644 src/Services/Location/Locations.API/Model/FrontierPoints.cs diff --git a/docker-compose.override.yml b/docker-compose.override.yml index a96720ba5..f23cd74f1 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -83,6 +83,10 @@ services: ports: - "5433:1433" + nosql.data: + ports: + - "27017:27017" + webstatus: environment: - ASPNETCORE_ENVIRONMENT=Development @@ -100,7 +104,8 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://0.0.0.0:80 - - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word + - ConnectionString=mongodb://nosql.data + - Database=LocationsDb - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: diff --git a/docker-compose.yml b/docker-compose.yml index e698992b2..01937cd38 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,6 +59,9 @@ services: sql.data: image: microsoft/mssql-server-linux + nosql.data: + image: mongo + basket.data: image: redis ports: @@ -81,4 +84,4 @@ services: context: ./src/Services/Location/Locations.API dockerfile: Dockerfile depends_on: - - sql.data \ No newline at end of file + - nosql.data \ No newline at end of file diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index dc2940b73..fed584f6b 100644 --- a/src/Services/Location/Locations.API/Controllers/LocationsController.cs +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -20,6 +20,24 @@ namespace Locations.API.Controllers _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); } + //GET api/v1/[controller]/1 + [Route("{userId:int}")] + [HttpGet] + public async Task GetUserLocation(int userId) + { + var userLocation = await _locationsService.GetUserLocation(userId); + return Ok(userLocation); + } + + //GET api/v1/[controller]/locations + [Route("locations")] + [HttpGet] + public async Task GetAllLocations() + { + var userLocation = await _locationsService.GetAllLocation(); + return Ok(userLocation); + } + //POST api/v1/[controller]/ [Route("")] [HttpPost] diff --git a/src/Services/Location/Locations.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs b/src/Services/Location/Locations.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs new file mode 100644 index 000000000..772129da9 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs @@ -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; + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Exceptions/LocationDomainException.cs b/src/Services/Location/Locations.API/Infrastructure/Exceptions/LocationDomainException.cs new file mode 100644 index 000000000..53016fcb2 --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Exceptions/LocationDomainException.cs @@ -0,0 +1,21 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions +{ + using System; + + /// + /// Exception type for app exceptions + /// + public class LocationDomainException : Exception + { + public LocationDomainException() + { } + + public LocationDomainException(string message) + : base(message) + { } + + public LocationDomainException(string message, Exception innerException) + : base(message, innerException) + { } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Location/Locations.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs new file mode 100644 index 000000000..30b9df1fa --- /dev/null +++ b/src/Services/Location/Locations.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -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 logger; + + public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger 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; } + } + } +} diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs index 5767694df..4289ffe68 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContext.cs @@ -1,72 +1,34 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure { - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Metadata.Builders; - using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; using Microsoft.eShopOnContainers.Services.Locations.API.Model; - using System.Threading; - using System.Threading.Tasks; + using Microsoft.Extensions.Options; + using MongoDB.Driver; - public class LocationsContext : DbContext, IUnitOfWork + public class LocationsContext { - public LocationsContext(DbContextOptions options) : base(options) - { - } + private readonly IMongoDatabase _database = null; - public DbSet Locations { get; set; } - public DbSet FrontierPoints { get; set; } - public DbSet UserLocation { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) + public LocationsContext(IOptions settings) { - builder.Entity(ConfigureLocations); - builder.Entity(ConfigureFrontierPoints); - builder.Entity(ConfigureUserLocation); + var client = new MongoClient(settings.Value.ConnectionString); + if (client != null) + _database = client.GetDatabase(settings.Value.Database); } - void ConfigureLocations(EntityTypeBuilder builder) + public IMongoCollection UserLocation { - builder.ToTable("Locations"); - - builder.HasKey(cl => cl.Id); - - builder.Property(cl => cl.Id) - .ForSqlServerUseSequenceHiLo("locations_seq") - .IsRequired(); - - builder.Property(cb => cb.Code) - .IsRequired() - .HasColumnName("LocationCode") - .HasMaxLength(15); - - builder.HasMany(f => f.Polygon) - .WithOne(l => l.Location) - .IsRequired(); - - builder.Property(cb => cb.Description) - .HasMaxLength(100); + get + { + return _database.GetCollection("UserLocation"); + } } - void ConfigureFrontierPoints(EntityTypeBuilder builder) + public IMongoCollection Locations { - builder.ToTable("FrontierPoints"); - - builder.HasKey(fp => fp.Id); - - builder.Property(fp => fp.Id) - .ForSqlServerUseSequenceHiLo("frontier_seq") - .IsRequired(); - } - - void ConfigureUserLocation(EntityTypeBuilder builder) - { - builder.ToTable("UserLocation"); - - builder.Property(ul => ul.Id) - .ForSqlServerUseSequenceHiLo("UserLocation_seq") - .IsRequired(); - - builder.HasIndex(ul => ul.UserId).IsUnique(); - } + get + { + return _database.GetCollection("Locations"); + } + } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs index 63404f49d..950698d97 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs @@ -1,131 +1,123 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure { using Microsoft.AspNetCore.Builder; - using Microsoft.EntityFrameworkCore; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using MongoDB.Bson; + using MongoDB.Driver; + using MongoDB.Driver.GeoJsonObjectModel; using System.Collections.Generic; - using System.Linq; using System.Threading.Tasks; - using Microsoft.eShopOnContainers.Services.Locations.API.Model; - using System.Text; public class LocationsContextSeed { + private static LocationsContext ctx; public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory) { - var context = (LocationsContext)applicationBuilder - .ApplicationServices.GetService(typeof(LocationsContext)); + var config = applicationBuilder + .ApplicationServices.GetRequiredService>(); - context.Database.Migrate(); + ctx = new LocationsContext(config); - if (!context.Locations.Any()) + if (!ctx.Locations.Database.GetCollection(nameof(Locations)).AsQueryable().Any()) { - context.Locations.AddRange( - GetPreconfiguredLocations()); - - await context.SaveChangesAsync(); - } - } - - static Locations GetPreconfiguredLocations() - { - var ww = new Locations("WW", "WorldWide", -1, -1); - ww.ChildLocations.Add(GetUSLocations()); - return ww; + await SetIndexes(); + await SetUSLocations(); + } } - static Locations GetUSLocations() + static async Task SetUSLocations() { - var us = new Locations("US", "United States", 41.650455, -101.357386, GetUSPoligon()); - us.ChildLocations.Add(GetWashingtonLocations()); - return us; - } - - static Locations GetWashingtonLocations() - { - var wht = new Locations("WHT", "Washington", 47.223652, -119.542781, GetWashingtonPoligon()); - wht.ChildLocations.Add(GetSeattleLocations()); - wht.ChildLocations.Add(GetRedmondLocations()); - return wht; - } - - static Locations GetSeattleLocations() - { - var bcn = new Locations("SEAT", "Seattle", 47.603111, -122.330747, GetSeattlePoligon()); - bcn.ChildLocations.Add(new Locations("SEAT-PioneerSqr", "Seattle Pioneer Square Shop", 47.602053, -122.333884, GetSeattlePioneerSqrPoligon())); - return bcn; + var us = new Locations() + { + Code = "US", + Description = "United States" + }; + us.SetLocation(-101.357386, 41.650455); + await ctx.Locations.InsertOneAsync(us); + await SetWashingtonLocations(us.Id); } - static Locations GetRedmondLocations() + static async Task SetWashingtonLocations(ObjectId parentId) { - var bcn = new Locations("REDM", "Redmond", 47.674961, -122.122887, GetRedmondPoligon()); - bcn.ChildLocations.Add(new Locations("REDM-DWNTWP", "Redmond Downtown Central Park Shop", 47.674433, -122.125006, GetRedmondDowntownParkPoligon())); - return bcn; - } - - static List GetUSPoligon() - { - var poligon = new List(); - poligon.Add(new FrontierPoints(48.7985, -62.88205)); - poligon.Add(new FrontierPoints(48.76513, -129.3132)); - poligon.Add(new FrontierPoints(30.12256, -120.9496)); - poligon.Add(new FrontierPoints(30.87114, -111.3944)); - poligon.Add(new FrontierPoints(24.24979, -78.11975)); - return poligon; + var wht = new Locations() + { + Parent_Id = parentId, + Code = "WHT", + Description = "Washington" + }; + wht.SetLocation(-119.542781, 47.223652); + await ctx.Locations.InsertOneAsync(wht); + await SetSeattleLocations(wht.Id); + await SetRedmondLocations(wht.Id); } - static List GetWashingtonPoligon() + static async Task SetSeattleLocations(ObjectId parentId) { - var poligon = new List(); - poligon.Add(new FrontierPoints(48.8943, -124.68633)); - poligon.Add(new FrontierPoints(45.66613, -124.32962)); - poligon.Add(new FrontierPoints(45.93384, -116.73824)); - poligon.Add(new FrontierPoints(49.04282, -116.96912)); - return poligon; + var stl = new Locations() + { + Parent_Id = parentId, + Code = "SEAT", + Description = "Seattle", + Polygon = GetSeattlePoligon() + }; + stl.SetLocation(-122.330747, 47.603111); + await ctx.Locations.InsertOneAsync(stl); } - static List GetSeattlePoligon() + static async Task SetRedmondLocations(ObjectId parentId) { - var poligon = new List(); - poligon.Add(new FrontierPoints(47.82929, -122.36238)); - poligon.Add(new FrontierPoints(47.6337, -122.42091)); - poligon.Add(new FrontierPoints(47.45224, -122.37371)); - poligon.Add(new FrontierPoints(47.50259, -122.20788)); - poligon.Add(new FrontierPoints(47.73644, -122.26934)); - return poligon; + var rdm = new Locations() + { + Parent_Id = parentId, + Code = "REDM", + Description = "Redmond", + Polygon = GetRedmondPoligon() + }; + rdm.SetLocation(-122.122887, 47.674961); + await ctx.Locations.InsertOneAsync(rdm); } - static List GetRedmondPoligon() + static async Task SetIndexes() { - var poligon = new List(); - poligon.Add(new FrontierPoints(47.73148, -122.15432)); - poligon.Add(new FrontierPoints(47.72559, -122.17673)); - poligon.Add(new FrontierPoints(47.67851, -122.16904)); - poligon.Add(new FrontierPoints(47.65036, -122.16136)); - poligon.Add(new FrontierPoints(47.62746, -122.15604)); - poligon.Add(new FrontierPoints(47.63463, -122.01562)); - poligon.Add(new FrontierPoints(47.74244, -122.04961)); - return poligon; - } + // Set location indexes + var builder = Builders.IndexKeys; + var keys = builder.Geo2DSphere(prop => prop.Location); + await ctx.Locations.Indexes.CreateOneAsync(keys); + } - static List GetSeattlePioneerSqrPoligon() + static GeoJsonPolygon GetSeattlePoligon() { - var poligon = new List(); - poligon.Add(new FrontierPoints(47.60338, -122.3327)); - poligon.Add(new FrontierPoints(47.60192, -122.33665)); - poligon.Add(new FrontierPoints(47.60096, -122.33456)); - poligon.Add(new FrontierPoints(47.60136, -122.33186)); - return poligon; + return new GeoJsonPolygon(new GeoJsonPolygonCoordinates( + new GeoJsonLinearRingCoordinates( + new List() + { + 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 GetRedmondDowntownParkPoligon() + static GeoJsonPolygon GetRedmondPoligon() { - var poligon = new List(); - poligon.Add(new FrontierPoints(47.67595, -122.12467)); - poligon.Add(new FrontierPoints(47.67449, -122.12862)); - poligon.Add(new FrontierPoints(47.67353, -122.12653)); - poligon.Add(new FrontierPoints(47.67368, -122.12197)); - return poligon; - } + return new GeoJsonPolygon(new GeoJsonPolygonCoordinates( + new GeoJsonLinearRingCoordinates( + new List() + { + new GeoJson2DGeographicCoordinates(47.73148, -122.15432), + new GeoJson2DGeographicCoordinates(47.72559, -122.17673), + new GeoJson2DGeographicCoordinates(47.67851, -122.16904), + new GeoJson2DGeographicCoordinates(47.65036, -122.16136), + new GeoJson2DGeographicCoordinates(47.62746, -122.15604), + new GeoJson2DGeographicCoordinates(47.63463, -122.01562), + new GeoJson2DGeographicCoordinates(47.74244, -122.04961), + new GeoJson2DGeographicCoordinates(47.73148, -122.15432), + }))); + } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs deleted file mode 100644 index 441ec1d82..000000000 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.Designer.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations -{ - [DbContext(typeof(LocationsContext))] - [Migration("20170601140634_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.1.2") - .HasAnnotation("SqlServer:Sequence:.frontier_seq", "'frontier_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.locations_seq", "'locations_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.UserLocation_seq", "'UserLocation_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("Latitude"); - - b.Property("LocationId") - .IsRequired(); - - b.Property("Longitude"); - - b.HasKey("Id"); - - b.HasIndex("LocationId"); - - b.ToTable("FrontierPoints"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("Code") - .IsRequired() - .HasColumnName("LocationCode") - .HasMaxLength(15); - - b.Property("Description") - .HasMaxLength(100); - - b.Property("Latitude"); - - b.Property("Longitude"); - - b.Property("ParentId"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("LocationId"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("LocationId"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("UserLocation"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany("Polygon") - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations") - .WithMany("ChildLocations") - .HasForeignKey("ParentId"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany() - .HasForeignKey("LocationId"); - }); - } - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs deleted file mode 100644 index 3073ca8ce..000000000 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/20170601140634_Initial.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; -using System.Text; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "frontier_seq", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "locations_seq", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "UserLocation_seq", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Locations", - columns: table => new - { - Id = table.Column(nullable: false), - LocationCode = table.Column(maxLength: 15, nullable: false), - Description = table.Column(maxLength: 100, nullable: true), - Latitude = table.Column(nullable: false), - Longitude = table.Column(nullable: false), - ParentId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Locations", x => x.Id); - table.ForeignKey( - name: "FK_Locations_Locations_ParentId", - column: x => x.ParentId, - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "FrontierPoints", - columns: table => new - { - Id = table.Column(nullable: false), - Latitude = table.Column(nullable: false), - LocationId = table.Column(nullable: false), - Longitude = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FrontierPoints", x => x.Id); - table.ForeignKey( - name: "FK_FrontierPoints_Locations_LocationId", - column: x => x.LocationId, - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "UserLocation", - columns: table => new - { - Id = table.Column(nullable: false), - LocationId = table.Column(nullable: true), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserLocation", x => x.Id); - table.ForeignKey( - name: "FK_UserLocation_Locations_LocationId", - column: x => x.LocationId, - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_FrontierPoints_LocationId", - table: "FrontierPoints", - column: "LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_Locations_ParentId", - table: "Locations", - column: "ParentId"); - - migrationBuilder.CreateIndex( - name: "IX_UserLocation_LocationId", - table: "UserLocation", - column: "LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_UserLocation_UserId", - table: "UserLocation", - column: "UserId", - unique: true); - - migrationBuilder.Sql(CreateGetDistanceFunction()); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "FrontierPoints"); - - migrationBuilder.DropTable( - name: "UserLocation"); - - migrationBuilder.DropTable( - name: "Locations"); - - migrationBuilder.DropSequence( - name: "frontier_seq"); - - migrationBuilder.DropSequence( - name: "locations_seq"); - - migrationBuilder.DropSequence( - name: "UserLocation_seq"); - - migrationBuilder.Sql(@"DROP FUNCTION IF EXISTS dbo.GetDistanceFromLocation"); - } - - private string CreateGetDistanceFunction() - { - var sb = new StringBuilder(); - sb.AppendLine(@"CREATE FUNCTION [dbo].[GetDistanceFromLocation]("); - sb.AppendLine(@"@CurrentLatitude float,"); - sb.AppendLine(@"@CurrentLongitude float,"); - sb.AppendLine(@"@latitude float,"); - sb.AppendLine(@"@longitude float)"); - sb.AppendLine(@"RETURNS int"); - sb.AppendLine(@"AS"); - sb.AppendLine(@"BEGIN"); - sb.AppendLine(@"DECLARE @geo1 geography = geography::Point(@CurrentLatitude, @CurrentLongitude, 4268),@geo2 geography = geography::Point(@latitude, @longitude, 4268)"); - sb.AppendLine(@"DECLARE @distance int"); - sb.AppendLine(@"SELECT @distance = @geo1.STDistance(@geo2)"); - sb.AppendLine(@"RETURN @distance"); - sb.AppendLine(@"END"); - return sb.ToString(); - } - - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs b/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs deleted file mode 100644 index c01237ab2..000000000 --- a/src/Services/Location/Locations.API/Infrastructure/Migrations/LocationsContextModelSnapshot.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Migrations -{ - [DbContext(typeof(LocationsContext))] - partial class LocationsContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.1.2") - .HasAnnotation("SqlServer:Sequence:.frontier_seq", "'frontier_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.locations_seq", "'locations_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:Sequence:.UserLocation_seq", "'UserLocation_seq', '', '1', '10', '', '', 'Int64', 'False'") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("Latitude"); - - b.Property("LocationId") - .IsRequired(); - - b.Property("Longitude"); - - b.HasKey("Id"); - - b.HasIndex("LocationId"); - - b.ToTable("FrontierPoints"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("Code") - .IsRequired() - .HasColumnName("LocationCode") - .HasMaxLength(15); - - b.Property("Description") - .HasMaxLength(100); - - b.Property("Latitude"); - - b.Property("Longitude"); - - b.Property("ParentId"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); - - b.Property("LocationId"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("LocationId"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("UserLocation"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.FrontierPoints", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany("Polygon") - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations") - .WithMany("ChildLocations") - .HasForeignKey("ParentId"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", "Location") - .WithMany() - .HasForeignKey("LocationId"); - }); - } - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs index bd3b58cb2..a0657e35d 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs @@ -1,17 +1,25 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories { using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using MongoDB.Bson; using System.Collections.Generic; using System.Threading.Tasks; - public interface ILocationsRepository : IRepository - { - UserLocation Add(UserLocation order); + public interface ILocationsRepository + { + Task GetAsync(ObjectId locationId); - void Update(UserLocation order); + Task> GetLocationListAsync(); - Task GetAsync(int userId); + Task GetUserLocationAsync(int userId); Task> GetNearestLocationListAsync(double lat, double lon); + + Task GetLocationByCurrentAreaAsync(Locations location); + + Task AddUserLocationAsync(UserLocation location); + + Task UpdateUserLocationAsync(UserLocation userLocation); + } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs deleted file mode 100644 index 946cf489c..000000000 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/IRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories -{ - public interface IRepository - { - IUnitOfWork UnitOfWork { get; } - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs deleted file mode 100644 index a81ac95e1..000000000 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/IUnitOfWork.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories -{ - public interface IUnitOfWork : IDisposable - { - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs index 98cb501b9..bb0039fd7 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs @@ -7,54 +7,67 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + using Microsoft.Extensions.Options; + using MongoDB.Driver; + using MongoDB.Driver.GeoJsonObjectModel; + using MongoDB.Driver.Builders; + using MongoDB.Bson; public class LocationsRepository : ILocationsRepository { - private readonly LocationsContext _context; + private readonly LocationsContext _context; - public IUnitOfWork UnitOfWork + public LocationsRepository(IOptions settings) { - get - { - return _context; - } + _context = new LocationsContext(settings); + } + + public async Task GetAsync(ObjectId locationId) + { + var filter = Builders.Filter.Eq("Id", locationId); + return await _context.Locations + .Find(filter) + .FirstOrDefaultAsync(); } - public LocationsRepository(LocationsContext context) + public async Task GetUserLocationAsync(int userId) { - _context = context ?? throw new ArgumentNullException(nameof(context)); + var filter = Builders.Filter.Eq("UserId", userId); + return await _context.UserLocation + .Find(filter) + .FirstOrDefaultAsync(); } - public UserLocation Add(UserLocation location) + public async Task> GetLocationListAsync() { - return _context.UserLocation.Add(location).Entity; - + return await _context.Locations.Find(new BsonDocument()).ToListAsync(); } - public async Task GetAsync(int userId) + public async Task> GetNearestLocationListAsync(double lat, double lon) { - return await _context.UserLocation - .Where(ul => ul.UserId == userId) - .SingleOrDefaultAsync(); - } + var point = GeoJson.Point(GeoJson.Geographic(lon, lat)); + var query = new FilterDefinitionBuilder().Near(x => x.Location, point); + return await _context.Locations.Find(query).ToListAsync(); + } - public async Task> GetNearestLocationListAsync(double lat, double lon) + public async Task GetLocationByCurrentAreaAsync(Locations location) { - var query = $"SELECT TOP(100) location.* " + - $"FROM[dbo].[Locations] AS location " + - $"ORDER BY [dbo].[GetDistanceFromLocation](location.Latitude, location.Longitude, " + - $"{lat.ToString(CultureInfo.InvariantCulture)}, " + - $"{lon.ToString(CultureInfo.InvariantCulture)})"; + var query = new FilterDefinitionBuilder().GeoIntersects("Location", location.Polygon); + return await _context.Locations.Find(query).FirstOrDefaultAsync(); + } - return await _context.Locations.FromSql(query) - .Include(f => f.Polygon) - .ToListAsync(); + public async Task AddUserLocationAsync(UserLocation location) + { + await _context.UserLocation.InsertOneAsync(location); } - public void Update(UserLocation location) + public async Task UpdateUserLocationAsync(UserLocation userLocation) { - _context.Entry(location).State = EntityState.Modified; + await _context.UserLocation.ReplaceOneAsync( + doc => doc.UserId == userLocation.UserId, + userLocation, + new UpdateOptions { IsUpsert = true }); } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs index 672dd2703..98f2904e1 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -1,10 +1,16 @@ 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 GetUserLocation(int id); + + Task> GetAllLocation(); + Task AddOrUpdateUserLocation(string userId, LocationRequest locRequest); } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index d2b721399..81f89b9d6 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -5,6 +5,9 @@ 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 { @@ -15,48 +18,53 @@ _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); } + public async Task GetUserLocation(int id) + { + return await _locationsRepository.GetUserLocationAsync(id); + } + + public async Task> GetAllLocation() + { + return await _locationsRepository.GetLocationListAsync(); + } + public async Task AddOrUpdateUserLocation(string id, LocationRequest currentPosition) { - int.TryParse(id, out int userId); - var currentUserLocation = await _locationsRepository.GetAsync(userId); - - // Get the nearest locations ordered by proximity + Locations currentUserAreaLocation = null; + + if (!int.TryParse(id, out int userId)) + { + throw new ArgumentException("Not valid userId"); + } + + // Get the nearest locations ordered var nearestLocationList = await _locationsRepository.GetNearestLocationListAsync(currentPosition.Latitude, currentPosition.Longitude); // Check out in which region we currently are - foreach(var locCandidate in nearestLocationList) + foreach(var locationCandidate in nearestLocationList.Where(x=> x.Polygon != null)) { - // Check location's tree and retrive user most specific area - var findNewLocationResult = locCandidate.GetUserMostSpecificLocation(currentPosition.Latitude, currentPosition.Longitude); - if (findNewLocationResult.isSuccess) - { - CreateUserLocation(currentUserLocation, findNewLocationResult.location, userId); - UpdateUserLocation(currentUserLocation, findNewLocationResult.location); - break; - } + currentUserAreaLocation = await _locationsRepository.GetLocationByCurrentAreaAsync(locationCandidate); + if(currentUserAreaLocation != null) { break; } } - var result = await _locationsRepository.UnitOfWork.SaveChangesAsync(); - return result > 0; - } - - private void CreateUserLocation(UserLocation currentUserLocation, Locations newLocation, int userId) - { - if (currentUserLocation is null) + if(currentUserAreaLocation is null) { - currentUserLocation = currentUserLocation ?? new UserLocation(userId); - currentUserLocation.Location = newLocation; - _locationsRepository.Add(currentUserLocation); - } - } + throw new LocationDomainException("User current area not found"); + } - private void UpdateUserLocation(UserLocation currentUserLocation, Locations newLocation) - { - if (currentUserLocation != null) + // If current area found, then update user location + if(currentUserAreaLocation != null) { - currentUserLocation.Location = newLocation; - _locationsRepository.Update(currentUserLocation); - } + var locationAncestors = new List(); + var userLocation = await _locationsRepository.GetUserLocationAsync(userId); + userLocation = userLocation ?? new UserLocation(); + userLocation.UserId = userId; + userLocation.LocationId = currentUserAreaLocation.Id; + userLocation.UpdateDate = DateTime.UtcNow; + await _locationsRepository.UpdateUserLocationAsync(userLocation); + } + + return true; } } } diff --git a/src/Services/Location/Locations.API/LocationSettings.cs b/src/Services/Location/Locations.API/LocationSettings.cs new file mode 100644 index 000000000..b3972239c --- /dev/null +++ b/src/Services/Location/Locations.API/LocationSettings.cs @@ -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; } + } +} diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj index 34808c5e0..57548fc03 100644 --- a/src/Services/Location/Locations.API/Locations.API.csproj +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -7,9 +7,6 @@ aspnet-Locations.API-20161122013619 - - - @@ -28,6 +25,10 @@ + + + + diff --git a/src/Services/Location/Locations.API/Model/FrontierPoints.cs b/src/Services/Location/Locations.API/Model/FrontierPoints.cs deleted file mode 100644 index e9061a88f..000000000 --- a/src/Services/Location/Locations.API/Model/FrontierPoints.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Locations.API.Model -{ - public class FrontierPoints - { - public int Id { get; set; } - public double Latitude { get; private set; } - public double Longitude { get; private set; } - - public FrontierPoints() - { - } - - public FrontierPoints(double latitude, double longitude) - { - Latitude = latitude; - Longitude = longitude; - } - - public Locations Location { get; private set; } - } -} \ No newline at end of file diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs index 71ffb603e..e35221bce 100644 --- a/src/Services/Location/Locations.API/Model/Locations.cs +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -1,94 +1,27 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; - - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model { + using MongoDB.Bson; + using MongoDB.Driver.GeoJsonObjectModel; + using System.Collections.Generic; + public class Locations { - public int Id { get; private set; } - public string Code { get; private set; } - public int? ParentId { get; private set; } - public string Description { get; private set; } - public double Latitude { get; private set; } - public double Longitude { get; private set; } - public (Locations location, bool isSuccess) GetUserMostSpecificLocation(double userLatitude, double userLongitude) - => CheckUserMostSpecificLocation(userLatitude, userLongitude); - - public Locations() - { - ChildLocations = new List(); - } - - public Locations(string code, string description, double latitude, double longitude, List polygon = null) : this() - { - Code = code; - Description = description; - Latitude = latitude; - Longitude = longitude; - Polygon = polygon ?? new List(); - } - - public virtual List Polygon { get; set; } - - [ForeignKey("ParentId")] - public virtual List ChildLocations { get; set; } - - private (Locations location, bool isSuccess) CheckUserMostSpecificLocation(double userLatitude, double userLongitude, Locations location = null) + public ObjectId Id { get; set; } + public string Code { get; set; } + public ObjectId Parent_Id { get; set; } + public string Description { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public GeoJsonPoint Location { get; set; } + public GeoJsonPolygon Polygon { get; set; } + public void SetLocation(double lon, double lat) => SetPosition(lon, lat); + + private void SetPosition(double lon, double lat) { - Locations result = this; - var childLocations = location != null ? location.ChildLocations : ChildLocations; - - // Check if user is in location's area, if not then returns false - if (!CheckIsPointInPolygon(userLatitude, userLongitude)) - { - return (this, false); - } - - foreach (var childLocation in childLocations) - { - result = childLocation; - - if (childLocation.ChildLocations.Count == 0){ break; } - - CheckUserMostSpecificLocation(userLatitude, userLongitude, childLocation); - } - return (result, true); - } - - private bool CheckIsPointInPolygon(double lat, double lon) - { - if(Polygon.Count == 0) { return false; }; - double minX = Polygon[0].Latitude; - double maxX = Polygon[0].Latitude; - double minY = Polygon[0].Longitude; - double maxY = Polygon[0].Longitude; - for (int i = 1; i < Polygon.Count; i++) - { - FrontierPoints q = Polygon[i]; - minX = Math.Min(q.Latitude, minX); - maxX = Math.Max(q.Latitude, maxX); - minY = Math.Min(q.Longitude, minY); - maxY = Math.Max(q.Longitude, maxY); - } - - if (lat < minX || lat > maxX || lon < minY || lon > maxY) - { - return false; - } - - bool inside = false; - for (int i = 0, j = Polygon.Count - 1; i < Polygon.Count; j = i++) - { - if ((Polygon[i].Longitude > lon) != (Polygon[j].Latitude > lat) && - lat < (Polygon[j].Longitude - Polygon[i].Latitude) * (lon - Polygon[i].Longitude) / (Polygon[j].Longitude - Polygon[i].Longitude) + Polygon[i].Latitude) - { - inside = !inside; - } - } - - return inside; + Latitude = lat; + Longitude = lon; + Location = new GeoJsonPoint( + new GeoJson2DGeographicCoordinates(lon, lat)); } } } diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs index f26196ace..ca60661c2 100644 --- a/src/Services/Location/Locations.API/Model/UserLocation.cs +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -1,24 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model { + using MongoDB.Bson.Serialization.Attributes; + using MongoDB.Bson; + using System; + using System.Collections.Generic; + public class UserLocation { - public int Id { get; set; } - public int UserId { get; set; } - - public UserLocation() - { - } - - public UserLocation(int userId) : this() - { - UserId = userId; - } - - public Locations Location { get; set; } + [BsonIgnoreIfDefault] + public ObjectId Id { get; set; } + public int UserId { get; set; } = 0; + public ObjectId LocationId { get; set; } + public DateTime UpdateDate { get; set; } } } diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index c13117b87..d601f26be 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -11,6 +11,7 @@ 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 { @@ -39,24 +40,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API public void ConfigureServices(IServiceCollection services) { // Add framework services. - services.AddMvc(); - - services.AddDbContext(options => + services.AddMvc(options => { - options.UseSqlServer(Configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); - - // Changing default behavior when client evaluation occurs to throw. - // Default in EF Core would be to log a warning when client evaluation is performed. - options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); - //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval - }); + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }).AddControllersAsServices(); + services.Configure(Configuration); + // Add framework services. services.AddSwaggerGen(options => { @@ -82,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API services.AddSingleton(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Location/Locations.API/appsettings.json b/src/Services/Location/Locations.API/appsettings.json index 812cce207..a280150a3 100644 --- a/src/Services/Location/Locations.API/appsettings.json +++ b/src/Services/Location/Locations.API/appsettings.json @@ -1,5 +1,6 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word;", + "ConnectionString": "mongodb://nosql.data", + "Database": "LocationsDb", "IdentityUrl": "http://localhost:5105", "Logging": { "IncludeScopes": false, diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs index 0c177d6f4..34b043909 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs @@ -1,4 +1,5 @@ -using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; +using Microsoft.eShopOnContainers.Services.Locations.API.Model; +using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; using Newtonsoft.Json; using System.Net.Http; using System.Text; @@ -11,26 +12,37 @@ namespace IntegrationTests.Services.Locations : LocationsScenarioBase { [Fact] - public async Task Set_new_user_location_response_ok_status_code() + public async Task Set_new_user_seattle_location_response_ok_status_code() { using (var server = CreateServer()) { - var content = new StringContent(BuildLocationsRequest(), UTF8Encoding.UTF8, "application/json"); + var userId = 1234; + var content = new StringContent(BuildLocationsRequest(-122.315752, 47.604610), UTF8Encoding.UTF8, "application/json"); var response = await server.CreateClient() .PostAsync(Post.AddNewLocation, content); response.EnsureSuccessStatusCode(); + + var userLocationResponse = await server.CreateClient() + .GetAsync(Get.LocationBy(userId)); + + var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); + var userLocation = JsonConvert.DeserializeObject(responseBody); + + response.EnsureSuccessStatusCode(); + + } } - string BuildLocationsRequest() + string BuildLocationsRequest(double lon, double lat) { var location = new LocationRequest() { - Longitude = -122.333875, - Latitude = 47.602050 - }; + Longitude = lon, + Latitude = lat + }; return JsonConvert.SerializeObject(location); } } diff --git a/test/Services/IntegrationTests/Services/Locations/appsettings.json b/test/Services/IntegrationTests/Services/Locations/appsettings.json index c31d6c9f1..9769fb65a 100644 --- a/test/Services/IntegrationTests/Services/Locations/appsettings.json +++ b/test/Services/IntegrationTests/Services/Locations/appsettings.json @@ -1,5 +1,6 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.LocationsDb;User Id=sa;Password=Pass@word;", + "ConnectionString": "mongodb://localhost:27017", + "Database": "LocationsDb", "ExternalCatalogBaseUrl": "http://localhost:5101", "IdentityUrl": "http://localhost:5105", "isTest": "true", From 73ac99bc70a1a9e0a25299ed5e0314fdce7e12a7 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Fri, 9 Jun 2017 11:59:56 +0200 Subject: [PATCH 4/8] Add locationService class --- .../Models/Location/LocationRequest.cs | 8 ++++++ .../Services/Location/ILocationService.cs | 10 +++++++ .../Services/Location/LocationService.cs | 28 +++++++++++++++++++ .../eShopOnContainers.Core.csproj | 5 +++- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/LocationRequest.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationService.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/LocationRequest.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/LocationRequest.cs new file mode 100644 index 000000000..99654914c --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Location/LocationRequest.cs @@ -0,0 +1,8 @@ +namespace eShopOnContainers.Core.Models.Location +{ + public class LocationRequest + { + public double Longitude { get; set; } + public double Latitude { get; set; } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationService.cs new file mode 100644 index 000000000..bc2c59915 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationService.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs new file mode 100644 index 000000000..2149a5118 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/LocationService.cs @@ -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); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj index d0dd19a46..00ba88226 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj @@ -1,4 +1,4 @@ - + @@ -70,6 +70,7 @@ + @@ -91,6 +92,8 @@ + + From c3b4a8e97e966eff262541bdd16fc50a2bdd488d Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Fri, 9 Jun 2017 12:01:47 +0200 Subject: [PATCH 5/8] Add LocationService to RegisterDependencies --- .../ViewModels/Base/ViewModelLocator.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs index 90abb8f09..3eda91b42 100755 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs @@ -11,6 +11,7 @@ using eShopOnContainers.Core.Services.Identity; using eShopOnContainers.Core.Services.Order; using eShopOnContainers.Core.Services.User; using Xamarin.Forms; +using eShopOnContainers.Core.Services.Location; namespace eShopOnContainers.Core.ViewModels.Base { @@ -53,24 +54,25 @@ namespace eShopOnContainers.Core.ViewModels.Base builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); - if (useMockServices) + if (useMockServices) { builder.RegisterInstance(new CatalogMockService()).As(); builder.RegisterInstance(new BasketMockService()).As(); builder.RegisterInstance(new OrderMockService()).As(); builder.RegisterInstance(new UserMockService()).As(); - UseMockService = true; + UseMockService = true; } else { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); - UseMockService = false; + UseMockService = false; } if (_container != null) From 7effb99503b0c715a2a3e00982b9b2a55c1d0cdc Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Fri, 9 Jun 2017 12:02:39 +0200 Subject: [PATCH 6/8] Add Location Setting objects to view and ViewModel --- .../ViewModels/SettingsViewModel.cs | 156 ++++++++++++++++-- .../Views/SettingsView.xaml | 77 ++++++++- 2 files changed, 214 insertions(+), 19 deletions(-) diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs index 4596eb25c..8bfe9ed25 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/SettingsViewModel.cs @@ -4,38 +4,51 @@ using Xamarin.Forms; using System.Threading.Tasks; using eShopOnContainers.Core.Helpers; using eShopOnContainers.Core.Models.User; +using System; +using eShopOnContainers.Core.Models.Location; +using eShopOnContainers.Core.Services.Location; namespace eShopOnContainers.Core.ViewModels { public class SettingsViewModel : ViewModelBase { - private string _title; - private string _description; + private string _titleUseAzureServices; + private string _descriptionUseAzureServices; private bool _useAzureServices; + private string _titleUseFakeLocation; + private string _descriptionUseFakeLocation; + private bool _useFakeLocation; private string _endpoint; + private double _latitude; + private double _longitude; + private ILocationService _locationService; - public SettingsViewModel() + public SettingsViewModel(ILocationService locationService) { UseAzureServices = !Settings.UseMocks; + _useFakeLocation = Settings.UseFakeLocation; + _latitude = Settings.FakeLatitude; + _longitude = Settings.FakeLongitude; + _locationService = locationService; } - public string Title + public string TitleUseAzureServices { - get { return _title; } + get { return _titleUseAzureServices; } set { - _title = value; - RaisePropertyChanged(() => Title); + _titleUseAzureServices = value; + RaisePropertyChanged(() => TitleUseAzureServices); } } - public string Description + public string DescriptionUseAzureServices { - get { return _description; } + get { return _descriptionUseAzureServices; } set { - _description = value; - RaisePropertyChanged(() => Description); + _descriptionUseAzureServices = value; + RaisePropertyChanged(() => DescriptionUseAzureServices); } } @@ -52,6 +65,39 @@ namespace eShopOnContainers.Core.ViewModels } } + public string TitleUseFakeLocation + { + get { return _titleUseFakeLocation; } + set + { + _titleUseFakeLocation = value; + RaisePropertyChanged(() => TitleUseFakeLocation); + } + } + + public string DescriptionUseFakeLocation + { + get { return _descriptionUseFakeLocation; } + set + { + _descriptionUseFakeLocation = value; + RaisePropertyChanged(() => DescriptionUseFakeLocation); + } + } + + public bool UseFakeLocation + { + get { return _useFakeLocation; } + set + { + _useFakeLocation = value; + + // Save use fake location services to local storage + Settings.UseFakeLocation = _useFakeLocation; + RaisePropertyChanged(() => UseFakeLocation); + } + } + public string Endpoint { get { return _endpoint; } @@ -68,12 +114,47 @@ namespace eShopOnContainers.Core.ViewModels } } + public double Latitude + { + get { return _latitude; } + set + { + _latitude = value; + + UpdateLatitude(_latitude); + + RaisePropertyChanged(() => Latitude); + } + } + + public double Longitude + { + get { return _longitude; } + set + { + _longitude = value; + + UpdateLongitude(_longitude); + + RaisePropertyChanged(() => Longitude); + } + } + public ICommand ToggleMockServicesCommand => new Command(async () => await ToggleMockServicesAsync()); + public ICommand ToggleFakeLocationCommand => new Command(() => ToggleFakeLocationAsync()); + + public ICommand ToggleSendLocationCommand => new Command(async () => await ToggleSendLocationAsync()); + public override Task InitializeAsync(object navigationData) { UpdateInfo(); + UpdateInfoFakeLocation(); + Endpoint = Settings.UrlBase; + _latitude = Settings.FakeLatitude; + _longitude = Settings.FakeLongitude; + _useFakeLocation = Settings.UseFakeLocation; return base.InitializeAsync(navigationData); } @@ -100,17 +181,48 @@ namespace eShopOnContainers.Core.ViewModels } } - private void UpdateInfo() + private void ToggleFakeLocationAsync() + { + ViewModelLocator.RegisterDependencies(!UseAzureServices); + UpdateInfoFakeLocation(); + } + + private async Task ToggleSendLocationAsync() + { + LocationRequest locationRequest = new LocationRequest + { + Latitude = _latitude, + Longitude = _longitude + }; + + await _locationService.UpdateUserLocation(locationRequest); + } + + private void UpdateInfo() { if (!UseAzureServices) { - Title = "Use Mock Services"; - Description = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach."; + TitleUseAzureServices = "Use Mock Services"; + DescriptionUseAzureServices = "Mock Services are simulated objects that mimic the behavior of real services using a controlled approach."; } else { - Title = "Use Microservices/Containers from eShopOnContainers"; - Description = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network."; + TitleUseAzureServices = "Use Microservices/Containers from eShopOnContainers"; + DescriptionUseAzureServices = "When enabling the use of microservices/containers, the app will attempt to use real services deployed as Docker containers at the specified base endpoint, which will must be reachable through the network."; + } + } + + private void UpdateInfoFakeLocation() + { + if (!UseFakeLocation) + { + TitleUseFakeLocation = "Use Fake Location"; + DescriptionUseFakeLocation = "Fake Location are added for marketing campaign testing."; + } + else + { + TitleUseFakeLocation = "Use Real Location"; + DescriptionUseFakeLocation = "When enabling the use of real location, the app will attempt to use real location from the device."; } } @@ -119,5 +231,17 @@ namespace eShopOnContainers.Core.ViewModels // Update remote endpoint (save to local storage) Settings.UrlBase = endpoint; } + + private void UpdateLatitude(double latitude) + { + // Update fake latitude (save to local storage) + Settings.FakeLatitude = latitude; + } + + private void UpdateLongitude(double longitude) + { + // Update fake longitude (save to local storage) + Settings.FakeLongitude = longitude; + } } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml index 7db37dd66..a8da35daa 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml @@ -108,11 +108,11 @@ Grid.Column="0" Grid.Row="1">