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] 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",