@ -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 | 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 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> Locations { get; set; } | |||||
public DbSet<FrontierPoints> FrontierPoints { get; set; } | |||||
public DbSet<UserLocation> UserLocation { get; set; } | |||||
protected override void OnModelCreating(ModelBuilder builder) | |||||
public LocationsContext(IOptions<LocationSettings> settings) | |||||
{ | { | ||||
builder.Entity<Locations>(ConfigureLocations); | |||||
builder.Entity<FrontierPoints>(ConfigureFrontierPoints); | |||||
builder.Entity<UserLocation>(ConfigureUserLocation); | |||||
var client = new MongoClient(settings.Value.ConnectionString); | |||||
if (client != null) | |||||
_database = client.GetDatabase(settings.Value.Database); | |||||
} | } | ||||
void ConfigureLocations(EntityTypeBuilder<Locations> builder) | |||||
public IMongoCollection<UserLocation> 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>("UserLocation"); | |||||
} | |||||
} | } | ||||
void ConfigureFrontierPoints(EntityTypeBuilder<FrontierPoints> builder) | |||||
public IMongoCollection<Locations> Locations | |||||
{ | { | ||||
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 | namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure | ||||
{ | { | ||||
using Microsoft.AspNetCore.Builder; | 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.Logging; | ||||
using Microsoft.Extensions.Options; | |||||
using MongoDB.Bson; | |||||
using MongoDB.Driver; | |||||
using MongoDB.Driver.GeoJsonObjectModel; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||||
using System.Text; | |||||
public class LocationsContextSeed | public class LocationsContextSeed | ||||
{ | { | ||||
private static LocationsContext ctx; | |||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory) | 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(); | |||||
} | |||||
} | |||||
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<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 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<FrontierPoints> GetWashingtonPoligon() | |||||
static async Task SetSeattleLocations(ObjectId parentId) | |||||
{ | { | ||||
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; | |||||
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<FrontierPoints> GetSeattlePoligon() | |||||
static async Task SetRedmondLocations(ObjectId parentId) | |||||
{ | { | ||||
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; | |||||
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> GetRedmondPoligon() | |||||
static async Task SetIndexes() | |||||
{ | { | ||||
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; | |||||
} | |||||
// Set location indexes | |||||
var builder = Builders<Locations>.IndexKeys; | |||||
var keys = builder.Geo2DSphere(prop => prop.Location); | |||||
await ctx.Locations.Indexes.CreateOneAsync(keys); | |||||
} | |||||
static List<FrontierPoints> GetSeattlePioneerSqrPoligon() | |||||
static GeoJsonPolygon<GeoJson2DGeographicCoordinates> GetSeattlePoligon() | |||||
{ | { | ||||
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; | |||||
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> GetRedmondDowntownParkPoligon() | |||||
static GeoJsonPolygon<GeoJson2DGeographicCoordinates> GetRedmondPoligon() | |||||
{ | { | ||||
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 | namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories | ||||
{ | { | ||||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | using Microsoft.eShopOnContainers.Services.Locations.API.Model; | ||||
using MongoDB.Bson; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading.Tasks; | 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<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)); | |||||
} | |||||
} |
@ -1,10 +1,16 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services | ||||
{ | { | ||||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; | ||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
public interface ILocationsService | public interface ILocationsService | ||||
{ | { | ||||
Task<UserLocation> GetUserLocation(int id); | |||||
Task<List<Locations>> GetAllLocation(); | |||||
Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest locRequest); | Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest locRequest); | ||||
} | } | ||||
} | } |
@ -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; } | |||||
} | |||||
} |
@ -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 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<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) | |||||
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); | |||||
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<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 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; } | |||||
} | } | ||||
} | } |