Merge branch 'feature/track-user-location-mongodb' of https://github.com/dotnet-architecture/eShopOnContainers into LocationSettingsXamarin
This commit is contained in:
		
						commit
						9c3ad2b2d2
					
				@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
      - nosql.data
 | 
			
		||||
@ -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<IActionResult> GetUserLocation(int userId)
 | 
			
		||||
        {
 | 
			
		||||
            var userLocation = await _locationsService.GetUserLocation(userId);
 | 
			
		||||
            return Ok(userLocation);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //GET api/v1/[controller]/locations
 | 
			
		||||
        [Route("locations")]
 | 
			
		||||
        [HttpGet]
 | 
			
		||||
        public async Task<IActionResult> GetAllLocations()
 | 
			
		||||
        {
 | 
			
		||||
            var userLocation = await _locationsService.GetAllLocation();
 | 
			
		||||
            return Ok(userLocation);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //POST api/v1/[controller]/
 | 
			
		||||
        [Route("")]
 | 
			
		||||
        [HttpPost]
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
 | 
			
		||||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.ActionResults
 | 
			
		||||
{
 | 
			
		||||
    public class InternalServerErrorObjectResult : ObjectResult
 | 
			
		||||
    {
 | 
			
		||||
        public InternalServerErrorObjectResult(object error)
 | 
			
		||||
            : base(error)
 | 
			
		||||
        {
 | 
			
		||||
            StatusCode = StatusCodes.Status500InternalServerError;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Exception type for app exceptions
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class LocationDomainException : Exception
 | 
			
		||||
    {
 | 
			
		||||
        public LocationDomainException()
 | 
			
		||||
        { }
 | 
			
		||||
 | 
			
		||||
        public LocationDomainException(string message)
 | 
			
		||||
            : base(message)
 | 
			
		||||
        { }
 | 
			
		||||
 | 
			
		||||
        public LocationDomainException(string message, Exception innerException)
 | 
			
		||||
            : base(message, innerException)
 | 
			
		||||
        { }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,67 @@
 | 
			
		||||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters
 | 
			
		||||
{
 | 
			
		||||
    using AspNetCore.Mvc;
 | 
			
		||||
    using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
    using Microsoft.AspNetCore.Mvc.Filters;
 | 
			
		||||
    using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.ActionResults;
 | 
			
		||||
    using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions;
 | 
			
		||||
    using Microsoft.Extensions.Logging;
 | 
			
		||||
    using System.Net;
 | 
			
		||||
 | 
			
		||||
    public class HttpGlobalExceptionFilter : IExceptionFilter
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IHostingEnvironment env;
 | 
			
		||||
        private readonly ILogger<HttpGlobalExceptionFilter> logger;
 | 
			
		||||
 | 
			
		||||
        public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
 | 
			
		||||
        {
 | 
			
		||||
            this.env = env;
 | 
			
		||||
            this.logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnException(ExceptionContext context)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogError(new EventId(context.Exception.HResult),
 | 
			
		||||
                context.Exception,
 | 
			
		||||
                context.Exception.Message);
 | 
			
		||||
 | 
			
		||||
            if (context.Exception.GetType() == typeof(LocationDomainException))
 | 
			
		||||
            {
 | 
			
		||||
                var json = new JsonErrorResponse
 | 
			
		||||
                {
 | 
			
		||||
                    Messages = new[] { context.Exception.Message }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
 | 
			
		||||
                //It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
 | 
			
		||||
                context.Result = new BadRequestObjectResult(json);
 | 
			
		||||
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var json = new JsonErrorResponse
 | 
			
		||||
                {
 | 
			
		||||
                    Messages = new[] { "An error occur.Try it again." }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if (env.IsDevelopment())
 | 
			
		||||
                {
 | 
			
		||||
                    json.DeveloperMessage = context.Exception;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
 | 
			
		||||
                // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
 | 
			
		||||
                context.Result = new InternalServerErrorObjectResult(json);
 | 
			
		||||
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
 | 
			
		||||
            }
 | 
			
		||||
            context.ExceptionHandled = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private class JsonErrorResponse
 | 
			
		||||
        {
 | 
			
		||||
            public string[] Messages { get; set; }
 | 
			
		||||
 | 
			
		||||
            public object DeveloperMessage { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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 LocationsContext(IOptions<LocationSettings> settings)
 | 
			
		||||
        {
 | 
			
		||||
            var client = new MongoClient(settings.Value.ConnectionString);
 | 
			
		||||
            if (client != null)
 | 
			
		||||
                _database = client.GetDatabase(settings.Value.Database);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public DbSet<Locations> Locations { get; set; }
 | 
			
		||||
        public DbSet<FrontierPoints> FrontierPoints { get; set; }
 | 
			
		||||
        public DbSet<UserLocation> UserLocation { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected override void OnModelCreating(ModelBuilder builder)
 | 
			
		||||
        public IMongoCollection<UserLocation> UserLocation
 | 
			
		||||
        {
 | 
			
		||||
            builder.Entity<Locations>(ConfigureLocations);
 | 
			
		||||
            builder.Entity<FrontierPoints>(ConfigureFrontierPoints);
 | 
			
		||||
            builder.Entity<UserLocation>(ConfigureUserLocation);
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return _database.GetCollection<UserLocation>("UserLocation");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void ConfigureLocations(EntityTypeBuilder<Locations> builder)
 | 
			
		||||
        public IMongoCollection<Locations> Locations
 | 
			
		||||
        {
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void ConfigureFrontierPoints(EntityTypeBuilder<FrontierPoints> builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.ToTable("FrontierPoints");
 | 
			
		||||
 | 
			
		||||
            builder.HasKey(fp => fp.Id);
 | 
			
		||||
 | 
			
		||||
            builder.Property(fp => fp.Id)
 | 
			
		||||
               .ForSqlServerUseSequenceHiLo("frontier_seq")
 | 
			
		||||
               .IsRequired();           
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void ConfigureUserLocation(EntityTypeBuilder<UserLocation> builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.ToTable("UserLocation");
 | 
			
		||||
 | 
			
		||||
            builder.Property(ul => ul.Id)
 | 
			
		||||
               .ForSqlServerUseSequenceHiLo("UserLocation_seq")
 | 
			
		||||
               .IsRequired();
 | 
			
		||||
 | 
			
		||||
            builder.HasIndex(ul => ul.UserId).IsUnique();
 | 
			
		||||
        }        
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return _database.GetCollection<Locations>("Locations");
 | 
			
		||||
            }
 | 
			
		||||
        }       
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,131 +1,123 @@
 | 
			
		||||
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;
 | 
			
		||||
    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.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
    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<IOptions<LocationSettings>>();
 | 
			
		||||
 | 
			
		||||
            context.Database.Migrate();
 | 
			
		||||
            ctx = new LocationsContext(config);
 | 
			
		||||
 | 
			
		||||
            if (!context.Locations.Any())
 | 
			
		||||
            if (!ctx.Locations.Database.GetCollection<Locations>(nameof(Locations)).AsQueryable().Any())
 | 
			
		||||
            {
 | 
			
		||||
                context.Locations.AddRange(
 | 
			
		||||
                    GetPreconfiguredLocations());
 | 
			
		||||
 | 
			
		||||
                await context.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
                await SetIndexes();
 | 
			
		||||
                await SetUSLocations();
 | 
			
		||||
            }            
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static Locations GetPreconfiguredLocations()
 | 
			
		||||
        static async Task SetUSLocations()
 | 
			
		||||
        {
 | 
			
		||||
            var ww = new Locations("WW", "WorldWide", -1, -1);
 | 
			
		||||
            ww.ChildLocations.Add(GetUSLocations());
 | 
			
		||||
            return ww;
 | 
			
		||||
            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 GetUSLocations()
 | 
			
		||||
        static async Task SetWashingtonLocations(ObjectId parentId)
 | 
			
		||||
        {
 | 
			
		||||
            var us = new Locations("US", "United States", 41.650455, -101.357386, GetUSPoligon());
 | 
			
		||||
            us.ChildLocations.Add(GetWashingtonLocations());
 | 
			
		||||
            return us;
 | 
			
		||||
            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 Locations GetWashingtonLocations()
 | 
			
		||||
        static async Task SetSeattleLocations(ObjectId parentId)
 | 
			
		||||
        {
 | 
			
		||||
            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 stl = new Locations()
 | 
			
		||||
            {
 | 
			
		||||
                Parent_Id = parentId,
 | 
			
		||||
                Code = "SEAT",
 | 
			
		||||
                Description = "Seattle",
 | 
			
		||||
                Polygon = GetSeattlePoligon()
 | 
			
		||||
            };
 | 
			
		||||
            stl.SetLocation(-122.330747, 47.603111);
 | 
			
		||||
            await ctx.Locations.InsertOneAsync(stl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static Locations GetRedmondLocations()
 | 
			
		||||
        static async Task SetRedmondLocations(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<FrontierPoints> GetUSPoligon()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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 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<FrontierPoints> GetWashingtonPoligon()
 | 
			
		||||
        static async Task SetIndexes()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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;
 | 
			
		||||
            // Set location indexes
 | 
			
		||||
            var builder = Builders<Locations>.IndexKeys;
 | 
			
		||||
            var keys = builder.Geo2DSphere(prop => prop.Location);
 | 
			
		||||
            await ctx.Locations.Indexes.CreateOneAsync(keys);
 | 
			
		||||
        }        
 | 
			
		||||
 | 
			
		||||
        static GeoJsonPolygon<GeoJson2DGeographicCoordinates> GetSeattlePoligon()
 | 
			
		||||
        {
 | 
			
		||||
            return new GeoJsonPolygon<GeoJson2DGeographicCoordinates>(new GeoJsonPolygonCoordinates<GeoJson2DGeographicCoordinates>(
 | 
			
		||||
                 new GeoJsonLinearRingCoordinates<GeoJson2DGeographicCoordinates>(
 | 
			
		||||
                     new List<GeoJson2DGeographicCoordinates>()
 | 
			
		||||
                     {
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.36238,47.82929),
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.42091,47.6337),
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.37371,47.45224),
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.20788,47.50259),
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.26934,47.73644),
 | 
			
		||||
                        new GeoJson2DGeographicCoordinates(-122.36238,47.82929)
 | 
			
		||||
                     })));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static List<FrontierPoints> GetSeattlePoligon()
 | 
			
		||||
        static GeoJsonPolygon<GeoJson2DGeographicCoordinates> GetRedmondPoligon()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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<FrontierPoints> GetRedmondPoligon()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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<FrontierPoints> GetSeattlePioneerSqrPoligon()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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<FrontierPoints> GetRedmondDowntownParkPoligon()
 | 
			
		||||
        {
 | 
			
		||||
            var poligon = new List<FrontierPoints>();
 | 
			
		||||
            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<GeoJson2DGeographicCoordinates>(new GeoJsonPolygonCoordinates<GeoJson2DGeographicCoordinates>(
 | 
			
		||||
                 new GeoJsonLinearRingCoordinates<GeoJson2DGeographicCoordinates>(
 | 
			
		||||
                     new List<GeoJson2DGeographicCoordinates>()
 | 
			
		||||
                     {
 | 
			
		||||
                        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),
 | 
			
		||||
                     })));
 | 
			
		||||
        }       
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Latitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("LocationId")
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Longitude");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("LocationId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("FrontierPoints");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Code")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnName("LocationCode")
 | 
			
		||||
                        .HasMaxLength(15);
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasMaxLength(100);
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Latitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Longitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("ParentId");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("ParentId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Locations");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("LocationId");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("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");
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<int>(nullable: false),
 | 
			
		||||
                    LocationCode = table.Column<string>(maxLength: 15, nullable: false),
 | 
			
		||||
                    Description = table.Column<string>(maxLength: 100, nullable: true),
 | 
			
		||||
                    Latitude = table.Column<double>(nullable: false),
 | 
			
		||||
                    Longitude = table.Column<double>(nullable: false),
 | 
			
		||||
                    ParentId = table.Column<int>(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<int>(nullable: false),
 | 
			
		||||
                    Latitude = table.Column<double>(nullable: false),
 | 
			
		||||
                    LocationId = table.Column<int>(nullable: false),
 | 
			
		||||
                    Longitude = table.Column<double>(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<int>(nullable: false),
 | 
			
		||||
                    LocationId = table.Column<int>(nullable: true),
 | 
			
		||||
                    UserId = table.Column<int>(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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "frontier_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Latitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("LocationId")
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Longitude");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("LocationId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("FrontierPoints");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "locations_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Code")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnName("LocationCode")
 | 
			
		||||
                        .HasMaxLength(15);
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasMaxLength(100);
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Latitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<double>("Longitude");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("ParentId");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("ParentId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Locations");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasAnnotation("SqlServer:HiLoSequenceName", "UserLocation_seq")
 | 
			
		||||
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("LocationId");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("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");
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<Locations> GetAsync(ObjectId locationId);
 | 
			
		||||
 | 
			
		||||
        void Update(UserLocation order);
 | 
			
		||||
        Task<List<Locations>> GetLocationListAsync();
 | 
			
		||||
 | 
			
		||||
        Task<UserLocation> GetAsync(int userId);
 | 
			
		||||
        Task<UserLocation> GetUserLocationAsync(int userId);
 | 
			
		||||
 | 
			
		||||
        Task<List<Locations>> GetNearestLocationListAsync(double lat, double lon);
 | 
			
		||||
 | 
			
		||||
        Task<Locations> GetLocationByCurrentAreaAsync(Locations location);
 | 
			
		||||
 | 
			
		||||
        Task AddUserLocationAsync(UserLocation location);
 | 
			
		||||
 | 
			
		||||
        Task UpdateUserLocationAsync(UserLocation userLocation);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<LocationSettings> settings)
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return _context;
 | 
			
		||||
            }
 | 
			
		||||
            _context = new LocationsContext(settings);
 | 
			
		||||
        }        
 | 
			
		||||
        
 | 
			
		||||
        public async Task<Locations> GetAsync(ObjectId locationId)
 | 
			
		||||
        {
 | 
			
		||||
            var filter = Builders<Locations>.Filter.Eq("Id", locationId);
 | 
			
		||||
            return await _context.Locations
 | 
			
		||||
                                 .Find(filter)
 | 
			
		||||
                                 .FirstOrDefaultAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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<UserLocation> GetAsync(int userId)
 | 
			
		||||
        public async Task<UserLocation> GetUserLocationAsync(int userId)
 | 
			
		||||
        {
 | 
			
		||||
            var filter = Builders<UserLocation>.Filter.Eq("UserId", userId);
 | 
			
		||||
            return await _context.UserLocation
 | 
			
		||||
                .Where(ul => ul.UserId == userId)
 | 
			
		||||
                .SingleOrDefaultAsync();
 | 
			
		||||
        }       
 | 
			
		||||
                                 .Find(filter)
 | 
			
		||||
                                 .FirstOrDefaultAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<List<Locations>> GetLocationListAsync()
 | 
			
		||||
        {
 | 
			
		||||
            return await _context.Locations.Find(new BsonDocument()).ToListAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<List<Locations>> 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();
 | 
			
		||||
            var point = GeoJson.Point(GeoJson.Geographic(lon, lat));
 | 
			
		||||
            var query = new FilterDefinitionBuilder<Locations>().Near(x => x.Location, point);
 | 
			
		||||
            return await _context.Locations.Find(query).ToListAsync(); 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Update(UserLocation location)
 | 
			
		||||
        public async Task<Locations> GetLocationByCurrentAreaAsync(Locations location)
 | 
			
		||||
        {
 | 
			
		||||
            _context.Entry(location).State = EntityState.Modified;
 | 
			
		||||
            var query = new FilterDefinitionBuilder<Locations>().GeoIntersects("Location", location.Polygon);
 | 
			
		||||
            return await _context.Locations.Find(query).FirstOrDefaultAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddUserLocationAsync(UserLocation location)
 | 
			
		||||
        {
 | 
			
		||||
            await _context.UserLocation.InsertOneAsync(location);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task UpdateUserLocationAsync(UserLocation userLocation)
 | 
			
		||||
        {
 | 
			
		||||
            await _context.UserLocation.ReplaceOneAsync(
 | 
			
		||||
                doc => doc.UserId == userLocation.UserId,
 | 
			
		||||
                userLocation,
 | 
			
		||||
                new UpdateOptions { IsUpsert = true });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<UserLocation> GetUserLocation(int id);
 | 
			
		||||
 | 
			
		||||
        Task<List<Locations>> GetAllLocation();
 | 
			
		||||
 | 
			
		||||
        Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest locRequest);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<UserLocation> GetUserLocation(int id)
 | 
			
		||||
        {
 | 
			
		||||
            return await _locationsRepository.GetUserLocationAsync(id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<List<Locations>> GetAllLocation()
 | 
			
		||||
        {
 | 
			
		||||
            return await _locationsRepository.GetLocationListAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> AddOrUpdateUserLocation(string id, LocationRequest currentPosition)
 | 
			
		||||
        {
 | 
			
		||||
            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<string>();
 | 
			
		||||
                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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								src/Services/Location/Locations.API/LocationSettings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Services/Location/Locations.API/LocationSettings.cs
									
									
									
									
									
										Normal file
									
								
							@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -7,9 +7,6 @@
 | 
			
		||||
    <UserSecretsId>aspnet-Locations.API-20161122013619</UserSecretsId>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Folder Include="Infrastructure\Migrations\" />
 | 
			
		||||
    <Folder Include="Infrastructure\Migrations\" />
 | 
			
		||||
    <Folder Include="Infrastructure\Migrations\" />
 | 
			
		||||
    <Folder Include="wwwroot\" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
@ -28,6 +25,10 @@
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
 | 
			
		||||
    <PackageReference Include="mongocsharpdriver" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="MongoDB.Bson" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="MongoDB.Driver" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="MongoDB.Driver.Core" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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 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<GeoJson2DGeographicCoordinates> Location { get; set; }
 | 
			
		||||
        public GeoJsonPolygon<GeoJson2DGeographicCoordinates> Polygon { get; set; }
 | 
			
		||||
        public void SetLocation(double lon, double lat) => SetPosition(lon, lat);
 | 
			
		||||
 | 
			
		||||
        public Locations()
 | 
			
		||||
        private void SetPosition(double lon, double lat)
 | 
			
		||||
        {
 | 
			
		||||
            ChildLocations = new List<Locations>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Locations(string code, string description, double latitude, double longitude, List<FrontierPoints> polygon = null) : this()
 | 
			
		||||
        {
 | 
			
		||||
            Code = code;
 | 
			
		||||
            Description = description;
 | 
			
		||||
            Latitude = latitude;
 | 
			
		||||
            Longitude = longitude;
 | 
			
		||||
            Polygon = polygon ?? new List<FrontierPoints>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual List<FrontierPoints> Polygon { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ForeignKey("ParentId")]
 | 
			
		||||
        public virtual List<Locations> 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;
 | 
			
		||||
            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<GeoJson2DGeographicCoordinates>(
 | 
			
		||||
                new GeoJson2DGeographicCoordinates(lon, lat));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<LocationsContext>(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<LocationSettings>(Configuration);
 | 
			
		||||
            
 | 
			
		||||
            // Add framework services.
 | 
			
		||||
            services.AddSwaggerGen(options =>
 | 
			
		||||
            {
 | 
			
		||||
@ -82,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
 | 
			
		||||
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 | 
			
		||||
            services.AddTransient<IIdentityService, IdentityService>();
 | 
			
		||||
            services.AddTransient<ILocationsService, LocationsService>();
 | 
			
		||||
            services.AddTransient<ILocationsRepository, LocationsRepository>();
 | 
			
		||||
            services.AddTransient<ILocationsRepository, LocationsRepository>();           
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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<UserLocation>(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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user