Implemented delivery model and filtering

This commit is contained in:
Egor 2024-10-30 06:11:07 +03:00
parent 9d232598cd
commit 8ffdea83c8
11 changed files with 124 additions and 39 deletions

View file

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="egor" />
</component>

View file

@ -9,8 +9,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0-rc.2.24474.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.10" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
</ItemGroup>

View file

@ -1,6 +1,8 @@
using Microsoft.EntityFrameworkCore;
namespace DeliveryWebApi.Model;
public class BaseEntity
{
public Guid Id { get; init; } = Guid.NewGuid();
public long Id { get; init; }
}

View file

@ -1,18 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace DeliveryWebApi.Model;
public class DeliveryContext(DbContextOptions<DeliveryContext> options) : DbContext(options)
{
public DbSet<District> Districts { get; init; }
public DbSet<Order> Orders { get; init; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<District>()
.HasMany(e => e.Orders)
.WithOne(e => e.District)
.HasForeignKey(e => e.DistrictId)
.IsRequired();
}
}

View file

@ -0,0 +1,46 @@
using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore;
namespace DeliveryWebApi.Model;
public class DeliveryDbContext(DbContextOptions<DeliveryDbContext> options) : DbContext(options)
{
public DbSet<District> Districts { get; init; }
public DbSet<Order> Orders { get; init; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<District>()
.HasMany(d => d.Orders)
.WithOne(o => o.District)
.HasForeignKey(o => o.DistrictId)
.IsRequired();
modelBuilder.Entity<District>()
.HasIndex(d => d.Name)
.IsUnique();
// random districts
var districtNames = new List<string>
{
"Viborgskiy", "Primorskiy", "Kalininskiy", "Petrogradskiy", "Center", "Admiralteyskiy", "Vasileostrovskiy",
"Kirovskiy", "Krasnoselskiy", "Moskovskiy", "Frunzenskiy", "Nevskiy", "Krasnogvardeyskiy", "Kolpinskiy",
"Pushkinskiy", "Pavlovskiy", "Petrodvortsoviy", "Lomonosovskiy", "Kurortniy", "Kronshtadskiy"
};
modelBuilder.Entity<District>()
.HasData(districtNames.Select((n, i) => new District { Id = i + 1, Name = n }));
// random orders
var rnd = new Random();
var startDate = new DateTime(2020, 1, 1);
var range = (DateTime.Today - startDate).Days;
modelBuilder.Entity<Order>()
.HasData(Enumerable.Range(1, 100).Select(i =>
{
var districtId = rnd.Next(0, districtNames.Count) + 1;
var weight = rnd.NextSingle() * 10 + 0.1f;
var date = startDate.AddDays(rnd.Next(range));
return new Order { Id = i, DistrictId = districtId, Weight = weight, DeliveryDate = date };
}));
}
}

View file

@ -1,6 +1,6 @@
namespace DeliveryWebApi.Model;
public class District(string name) : BaseEntity
public class District : BaseEntity
{
public string Name { get; init; }
public ICollection<Order> Orders { get; init; } = new List<Order>();

View file

@ -3,7 +3,7 @@ namespace DeliveryWebApi.Model;
public class Order : BaseEntity
{
public float Weight { get; init; }
public Guid DistrictId { get; init; }
public long DistrictId { get; init; }
public District District { get; init; } = null!;
public DateTime DeliveryDate { get; init; }
}

View file

@ -1,38 +1,86 @@
using DeliveryWebApi.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
if (builder.Configuration.GetValue<bool>("UseInMemoryDatabase"))
builder.Services.AddDbContext<DeliveryContext>(options => options.UseInMemoryDatabase("Delivery"));
else
// storage setup
// either use in-memory storage or postgres depending on cfg
builder.Services.AddDbContext<DeliveryDbContext>(options =>
{
builder.Services.AddDbContext<DeliveryContext>(options =>
if (builder.Configuration.GetValue<bool>("UseInMemoryDatabase"))
options.UseInMemoryDatabase("Delivery");
else
{
var connectionString = builder.Configuration.GetConnectionString("DatabaseConnection");
options.UseNpgsql(connectionString);
});
}
}
});
var app = builder.Build();
// swagger setup
if (app.Environment.IsDevelopment())
{
// swagger setup
app.UseSwagger();
app.UseSwaggerUI();
// migrate model to seed random test values for developers
app.Services.CreateScope().ServiceProvider.GetRequiredService<DeliveryDbContext>().Database.EnsureCreated();
}
app.UseHttpsRedirection();
app.MapPut("/addOrder", (float weight, long districtId, DateTime deliveryTime) =>
{
app.MapPut("/addDistrict", (DeliveryDbContext context, string name) =>
{
if (context.Districts.Any(d => d.Name == name))
return Results.BadRequest(new { Error = "District with the same name already exists" });
var district = new District{ Name = name };
context.Districts.Add(district);
var updated = context.SaveChanges();
if (updated <= 0)
return Results.Json(new { Error = "Something went wrong while adding a new district" }, statusCode: 500);
return Results.Ok(new { district.Id });
});
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.MapGet("/getDistrict/{id:long}", (DeliveryDbContext context, long id) =>
{
var district = context.Districts.FirstOrDefault(d => d.Id == id);
if (district == null)
return Results.NotFound(new { Error = "District with the given id does not exist" });
return Results.Ok(new { district.Id, district.Name });
});
app.MapPut("/addOrder", (DeliveryDbContext context, long districtId, float weight, DateTime deliveryDate) =>
{
var order = new Order{ DistrictId = districtId, Weight = weight, DeliveryDate = deliveryDate };
context.Orders.Add(order);
var updated = context.SaveChanges();
if (updated <= 0)
return Results.Json(new { Error = "Something went wrong while adding a new order" }, statusCode: 500);
return Results.Ok(new { order.Id });
});
app.MapGet("/getOrder/{id:long}", (DeliveryDbContext context, long id) =>
{
var order = context.Orders.FirstOrDefault(o => o.Id == id);
if (order == null)
return Results.NotFound(new { Error = "Order with the given id does not exist" });
return Results.Ok(new { order.Id, order.DistrictId, order.Weight, order.DeliveryDate });
});
app.MapGet("/getOrders", (DeliveryDbContext context, string? districtName, DateTime? firstDeliveryDate) =>
{
var orders = context.Districts
.Where(d => districtName == null || d.Name == districtName)
.SelectMany(d => d.Orders)
.Where(o => firstDeliveryDate == null || o.DeliveryDate >= firstDeliveryDate)
.OrderBy(o => o.DeliveryDate)
.Select(o => new { o.Id, o.District.Name, o.DeliveryDate, o.Weight });
return Results.Ok(orders);
});
app.Run();

View file

@ -4,5 +4,6 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
},
"UseInMemoryDatabase": true
}

View file

@ -5,5 +5,6 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"UseInMemoryDatabase": true
}

View file

@ -1,2 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADateTime_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F14a2b70486934722acea07b38045413ab2d200_003F3e_003F15560e7c_003FDateTime_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResults_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F18bfc57084254648b9740270d0ce5e20ed10_003Fea_003Fc087adef_003FResults_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebApplication_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F847c7bc70f83436e9c281d8cea5531a17d10_003F7f_003F26f6c2e2_003FWebApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>