@ -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 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 | |||
{ | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using MongoDB.Bson; | |||
using MongoDB.Driver; | |||
using MongoDB.Driver.GeoJsonObjectModel; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.eShopOnContainers.Services.Locations.API.Model; | |||
using System.Text; | |||
public class LocationsContextSeed | |||
{ | |||
private static LocationsContext ctx; | |||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory) | |||
{ | |||
var context = (LocationsContext)applicationBuilder | |||
.ApplicationServices.GetService(typeof(LocationsContext)); | |||
var config = applicationBuilder | |||
.ApplicationServices.GetRequiredService<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 | |||
{ | |||
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)); | |||
} | |||
} |
@ -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); | |||
} | |||
} |
@ -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 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 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; } | |||
} | |||
} |