feat: add "Popular stations" statistic
This commit is contained in:
parent
3d6ffa25ab
commit
1de4e24f22
@ -54,9 +54,18 @@ public class StatisticsController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("stations")]
|
||||
public async Task<IActionResult> GetPopularStations([FromQuery] int amount = 10)
|
||||
public async Task<IActionResult> GetPopularStations([FromQuery] PopularAddressesParameters parameters)
|
||||
{
|
||||
return Ok();
|
||||
var result = await _statisticsService.GetPopularStations(parameters);
|
||||
|
||||
if (!result.IsSucceed)
|
||||
{
|
||||
return BadRequest(result.message);
|
||||
}
|
||||
|
||||
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(result.pagingMetadata));
|
||||
|
||||
return Ok(result.stations);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ public class ApplicationDbContext : IdentityDbContext<User>
|
||||
public DbSet<City> Cities { get; set; } = null!;
|
||||
public DbSet<State> States { get; set; } = null!;
|
||||
public DbSet<Country> Countries { get; set; } = null!;
|
||||
public DbSet<TicketGroup> TicketGroups { get; set; } = null!;
|
||||
public DbSet<Ticket> Tickets { get; set; } = null!;
|
||||
public DbSet<Review> Reviews { get; set; } = null!;
|
||||
}
|
860
Server/Migrations/20221124184433_Add_TicketGroups.Designer.cs
generated
Normal file
860
Server/Migrations/20221124184433_Add_TicketGroups.Designer.cs
generated
Normal file
@ -0,0 +1,860 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Server.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20221124184433_Add_TicketGroups")]
|
||||
partial class Add_TicketGroups
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Address", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("CityId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("Latitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<double>("Longitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CityId");
|
||||
|
||||
b.ToTable("Addresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.City", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StateId");
|
||||
|
||||
b.ToTable("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Company", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OwnerId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Companies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Country", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Countries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Review", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("PostDateTimeUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("VehicleEnrollmentId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("VehicleEnrollmentId");
|
||||
|
||||
b.ToTable("Reviews");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Route", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Routes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.RouteAddress", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AddressId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("CostToNextCity")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("RouteId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<TimeSpan>("TimeSpanToNextCity")
|
||||
.HasColumnType("interval");
|
||||
|
||||
b.Property<TimeSpan>("WaitTimeSpan")
|
||||
.HasColumnType("interval");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AddressId");
|
||||
|
||||
b.HasIndex("RouteId");
|
||||
|
||||
b.ToTable("RouteAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.State", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("CountryId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CountryId");
|
||||
|
||||
b.ToTable("States");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Ticket", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("FirstRouteAddressId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("IsMissed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsReturned")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("LastRouteAddressId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("PurchaseDateTimeUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("TicketGroupId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("VehicleEnrollmentId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TicketGroupId");
|
||||
|
||||
b.HasIndex("VehicleEnrollmentId");
|
||||
|
||||
b.ToTable("Tickets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TicketGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Vehicle", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("Capacity")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("CompanyId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("HasBelts")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasClimateControl")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasOutlet")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasStewardess")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasTV")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasWC")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("HasWiFi")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CompanyId");
|
||||
|
||||
b.ToTable("Vehicles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CancelationComment")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<TimeSpan?>("DelayTimeSpan")
|
||||
.HasColumnType("interval");
|
||||
|
||||
b.Property<DateTime>("DepartureDateTimeUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("IsCanceled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("RouteId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("VehicleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RouteId");
|
||||
|
||||
b.HasIndex("VehicleId");
|
||||
|
||||
b.ToTable("VehicleEnrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Server.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Address", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.City", "City")
|
||||
.WithMany("Addresses")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.City", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.State", "State")
|
||||
.WithMany("Cities")
|
||||
.HasForeignKey("StateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("State");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Company", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Review", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", "User")
|
||||
.WithMany("Reviews")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Server.Models.VehicleEnrollment", "VehicleEnrollment")
|
||||
.WithMany("Reviews")
|
||||
.HasForeignKey("VehicleEnrollmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("VehicleEnrollment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.RouteAddress", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.Address", "Address")
|
||||
.WithMany("RouteAddresses")
|
||||
.HasForeignKey("AddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Server.Models.Route", "Route")
|
||||
.WithMany("RouteAddresses")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Address");
|
||||
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.State", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.Country", "Country")
|
||||
.WithMany("States")
|
||||
.HasForeignKey("CountryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Ticket", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.TicketGroup", "TicketGroup")
|
||||
.WithMany("Tickets")
|
||||
.HasForeignKey("TicketGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Server.Models.VehicleEnrollment", "VehicleEnrollment")
|
||||
.WithMany("Tickets")
|
||||
.HasForeignKey("VehicleEnrollmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("TicketGroup");
|
||||
|
||||
b.Navigation("VehicleEnrollment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", "User")
|
||||
.WithMany("TicketGroups")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.OwnsMany("Server.Models.RefreshToken", "RefreshTokens", b1 =>
|
||||
{
|
||||
b1.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<DateTime>("CreationDateTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b1.Property<DateTime>("ExpiryDateTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b1.Property<DateTime?>("Revoked")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b1.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.HasKey("UserId", "Id");
|
||||
|
||||
b1.ToTable("RefreshToken");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
b.Navigation("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Vehicle", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.Company", "Company")
|
||||
.WithMany("Vehicles")
|
||||
.HasForeignKey("CompanyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Company");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.VehicleEnrollment", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.Route", "Route")
|
||||
.WithMany("VehicleEnrollments")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Server.Models.Vehicle", "Vehicle")
|
||||
.WithMany("VehicleEnrollments")
|
||||
.HasForeignKey("VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Route");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Address", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.City", b =>
|
||||
{
|
||||
b.Navigation("Addresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Company", b =>
|
||||
{
|
||||
b.Navigation("Vehicles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Country", b =>
|
||||
{
|
||||
b.Navigation("States");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Route", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
|
||||
b.Navigation("VehicleEnrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.State", b =>
|
||||
{
|
||||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.Navigation("Tickets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Reviews");
|
||||
|
||||
b.Navigation("TicketGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Vehicle", b =>
|
||||
{
|
||||
b.Navigation("VehicleEnrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Navigation("Reviews");
|
||||
|
||||
b.Navigation("Tickets");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
129
Server/Migrations/20221124184433_Add_TicketGroups.cs
Normal file
129
Server/Migrations/20221124184433_Add_TicketGroups.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Server.Migrations
|
||||
{
|
||||
public partial class Add_TicketGroups : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tickets_AspNetUsers_UserId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tickets_UserId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "FirstRouteAddressId",
|
||||
table: "Tickets",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "LastRouteAddressId",
|
||||
table: "Tickets",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "TicketGroupId",
|
||||
table: "Tickets",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TicketGroups",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TicketGroups", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TicketGroups_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tickets_TicketGroupId",
|
||||
table: "Tickets",
|
||||
column: "TicketGroupId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TicketGroups_UserId",
|
||||
table: "TicketGroups",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tickets_TicketGroups_TicketGroupId",
|
||||
table: "Tickets",
|
||||
column: "TicketGroupId",
|
||||
principalTable: "TicketGroups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tickets_TicketGroups_TicketGroupId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TicketGroups");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tickets_TicketGroupId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FirstRouteAddressId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastRouteAddressId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TicketGroupId",
|
||||
table: "Tickets");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "UserId",
|
||||
table: "Tickets",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tickets_UserId",
|
||||
table: "Tickets",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tickets_AspNetUsers_UserId",
|
||||
table: "Tickets",
|
||||
column: "UserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -363,31 +363,55 @@ namespace Server.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("FirstRouteAddressId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("IsMissed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsReturned")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("LastRouteAddressId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("PurchaseDateTimeUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
b.Property<int>("TicketGroupId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("VehicleEnrollmentId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
b.HasIndex("TicketGroupId");
|
||||
|
||||
b.HasIndex("VehicleEnrollmentId");
|
||||
|
||||
b.ToTable("Tickets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TicketGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -678,9 +702,9 @@ namespace Server.Migrations
|
||||
|
||||
modelBuilder.Entity("Server.Models.Ticket", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", "User")
|
||||
b.HasOne("Server.Models.TicketGroup", "TicketGroup")
|
||||
.WithMany("Tickets")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TicketGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@ -690,11 +714,22 @@ namespace Server.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
b.Navigation("TicketGroup");
|
||||
|
||||
b.Navigation("VehicleEnrollment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.HasOne("Server.Models.User", "User")
|
||||
.WithMany("TicketGroups")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.OwnsMany("Server.Models.RefreshToken", "RefreshTokens", b1 =>
|
||||
@ -794,11 +829,16 @@ namespace Server.Migrations
|
||||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.TicketGroup", b =>
|
||||
{
|
||||
b.Navigation("Tickets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Reviews");
|
||||
|
||||
b.Navigation("Tickets");
|
||||
b.Navigation("TicketGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Server.Models.Vehicle", b =>
|
||||
|
@ -9,14 +9,16 @@ public class Ticket
|
||||
public int Id { get; set; }
|
||||
|
||||
[ForeignKey("UserId")]
|
||||
public string UserId { get; set; } = null!;
|
||||
public User User { get; set; } = null!;
|
||||
public int TicketGroupId { get; set; }
|
||||
public TicketGroup TicketGroup { get; set; } = null!;
|
||||
|
||||
[ForeignKey("VehicleEnrollmentId")]
|
||||
public int VehicleEnrollmentId { get; set; }
|
||||
public VehicleEnrollment VehicleEnrollment { get; set; } = null!;
|
||||
|
||||
public DateTime PurchaseDateTimeUtc { get; set; } = DateTime.UtcNow;
|
||||
public int FirstRouteAddressId { get; set; }
|
||||
public int LastRouteAddressId { get; set; }
|
||||
public bool IsReturned { get; set; } = false;
|
||||
public bool IsMissed { get; set; } = false;
|
||||
}
|
14
Server/Models/TicketGroup.cs
Normal file
14
Server/Models/TicketGroup.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Server.Models;
|
||||
|
||||
public class TicketGroup
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string UserId { get; set; } = null!;
|
||||
public User User { get; set; } = null!;
|
||||
|
||||
public virtual IList<Ticket> Tickets { get; set; }
|
||||
}
|
@ -7,6 +7,6 @@ public class User : IdentityUser
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public IList<RefreshToken> RefreshTokens { get; set; } = null!;
|
||||
public virtual IList<Ticket> Tickets { get; set; } = null!;
|
||||
public virtual IList<TicketGroup> TicketGroups { get; set; } = null!;
|
||||
public virtual IList<Review> Reviews { get; set; } = null!;
|
||||
}
|
@ -134,6 +134,7 @@ builder.Services.AddScoped<IDataShaper<RouteAddress>, DataShaper<RouteAddress>>(
|
||||
|
||||
builder.Services.AddScoped<IDataShaper<UserDto>, DataShaper<UserDto>>();
|
||||
builder.Services.AddScoped<IDataShaper<CompanyDto>, DataShaper<CompanyDto>>();
|
||||
builder.Services.AddScoped<IDataShaper<AddressDto>, DataShaper<AddressDto>>();
|
||||
|
||||
// Adding DB Context with PostgreSQL
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
|
@ -10,12 +10,15 @@ public interface IStatisticsService
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> route)>
|
||||
GetPopularRoutes(int amount);
|
||||
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users, PagingMetadata<User> pagingMetadata)>
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetEngagedUsers(EngagedUserParameters parameters);
|
||||
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies, PagingMetadata<Company> pagingMetadata)>
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetPopularCompanies(PopularCompanyParameters parameters);
|
||||
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations)>
|
||||
GetPopularStations(int amount);
|
||||
Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetPopularStations(PopularAddressesParameters parameters);
|
||||
}
|
@ -16,14 +16,17 @@ public class StatisticsService : IStatisticsService
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IDataShaper<UserDto> _userDataShaper;
|
||||
private readonly IDataShaper<CompanyDto> _companyDataShaper;
|
||||
private readonly IDataShaper<AddressDto> _addressDataShaper;
|
||||
|
||||
public StatisticsService(ApplicationDbContext dbContext, IMapper mapper,
|
||||
IDataShaper<UserDto> userDataShaper, IDataShaper<CompanyDto> companyDataShaper)
|
||||
IDataShaper<UserDto> userDataShaper, IDataShaper<CompanyDto> companyDataShaper,
|
||||
IDataShaper<AddressDto> addressDataShaper)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_mapper = mapper;
|
||||
_userDataShaper = userDataShaper;
|
||||
_companyDataShaper = companyDataShaper;
|
||||
_addressDataShaper = addressDataShaper;
|
||||
}
|
||||
|
||||
// Popularity is measured in number of purchased tickets
|
||||
@ -33,48 +36,56 @@ public class StatisticsService : IStatisticsService
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Engagement is measured in number of tickets bought in last 60 days
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users, PagingMetadata<User> pagingMetadata)>
|
||||
// Engagement is measured in number of purchases made in past N days
|
||||
// One purchase contains one (direct route) or more (route with transfers) tickets
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> users,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetEngagedUsers(EngagedUserParameters parameters)
|
||||
{
|
||||
var fromDateUtc = DateTime.UtcNow - TimeSpan.FromDays(parameters.Days ?? parameters.DefaultDays);
|
||||
|
||||
var fromDateUtc =
|
||||
DateTime.UtcNow - TimeSpan.FromDays(parameters.Days ?? parameters.DefaultDays);
|
||||
|
||||
var resultObjects = _dbContext.Users
|
||||
.Include(u => u.Tickets)
|
||||
.Include(u => u.TicketGroups)
|
||||
.ThenInclude(tg => tg.Tickets)
|
||||
.Select(u => new
|
||||
{
|
||||
User = u,
|
||||
Tickets = u.Tickets.Where(t => t.PurchaseDateTimeUtc >= fromDateUtc)
|
||||
TicketGroups = u.TicketGroups.Where(tg =>
|
||||
tg.Tickets.First().PurchaseDateTimeUtc >= fromDateUtc)
|
||||
})
|
||||
.OrderByDescending(o => o.User.Tickets.Count)
|
||||
.OrderByDescending(o => o.TicketGroups.Count())
|
||||
.Take(parameters.Amount);
|
||||
|
||||
|
||||
var dbUsers = resultObjects.Select(i => i.User);
|
||||
var pagingMetadata = ApplyPaging(ref dbUsers, parameters.PageNumber,
|
||||
parameters.PageSize);
|
||||
|
||||
var userDtos = _mapper.ProjectTo<UserDto>(dbUsers).ToArray();
|
||||
var shapedData = _userDataShaper
|
||||
var shapedDataArray = _userDataShaper
|
||||
.ShapeData(userDtos, parameters.Fields ?? parameters.DefaultFields)
|
||||
.ToArray();
|
||||
|
||||
|
||||
if (parameters.Fields != null &&
|
||||
parameters.Fields.ToLower().Contains("ticketCount".ToLower()))
|
||||
parameters.Fields.ToLower().Contains("purchaseCount".ToLower()))
|
||||
{
|
||||
var dbUsersArray = await dbUsers.ToArrayAsync();
|
||||
for (int i = 0; i < dbUsersArray.Length; i++)
|
||||
{
|
||||
var ticketCount = dbUsersArray[i].Tickets.Count;
|
||||
shapedData[i].TryAdd("TicketCount", ticketCount);
|
||||
var ticketCount = dbUsersArray[i].TicketGroups.Count;
|
||||
shapedDataArray[i].TryAdd("PurchaseCount", ticketCount);
|
||||
}
|
||||
}
|
||||
|
||||
return (true, null, shapedData, pagingMetadata);
|
||||
var shapedData = shapedDataArray.AsQueryable();
|
||||
var pagingMetadata = ApplyPaging(ref shapedData, parameters.PageNumber,
|
||||
parameters.PageSize);
|
||||
shapedDataArray = shapedData.ToArray();
|
||||
|
||||
return (true, null, shapedDataArray, pagingMetadata);
|
||||
}
|
||||
|
||||
// Popularity is measured in average rating of all VehicleEnrollments of a company
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies, PagingMetadata<Company> pagingMetadata)>
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> companies,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetPopularCompanies(PopularCompanyParameters parameters)
|
||||
{
|
||||
var dbCompanies = _dbContext.Companies
|
||||
@ -132,33 +143,133 @@ public class StatisticsService : IStatisticsService
|
||||
}
|
||||
}
|
||||
|
||||
companiesAvgRatings = companiesAvgRatings.Skip(companiesAvgRatings.Length - parameters.Amount).Reverse().ToArray();
|
||||
var popularCompanies = dbCompaniesArray.Skip(companiesAvgRatings.Length - parameters.Amount).Reverse().AsQueryable();
|
||||
companiesAvgRatings = companiesAvgRatings
|
||||
.Skip(companiesAvgRatings.Length - parameters.Amount).Reverse()
|
||||
.ToArray();
|
||||
var popularCompanies = dbCompaniesArray
|
||||
.Skip(companiesAvgRatings.Length - parameters.Amount).Reverse()
|
||||
.AsQueryable();
|
||||
|
||||
// Apply paging, convert to DTOs and shape data
|
||||
|
||||
var pagingMetadata = ApplyPaging(ref popularCompanies, parameters.PageNumber, parameters.PageSize);
|
||||
var companyDtos = _mapper.ProjectTo<CompanyDto>(popularCompanies).ToArray();
|
||||
// Convert to DTOs, shape data and apply paging
|
||||
|
||||
var shapedData = _companyDataShaper.ShapeData(companyDtos, parameters.Fields ?? parameters.DefaultFields).ToArray();
|
||||
var companyDtos = _mapper.ProjectTo<CompanyDto>(popularCompanies);
|
||||
var shapedDataArray = _companyDataShaper.ShapeData(companyDtos,
|
||||
parameters.Fields ?? parameters.DefaultFields).ToArray();
|
||||
|
||||
if (parameters.Fields != null &&
|
||||
parameters.Fields.ToLower().Contains("rating".ToLower()))
|
||||
{
|
||||
for (int i = 0; i < shapedData.Length; i++)
|
||||
for (int i = 0; i < shapedDataArray.Length; i++)
|
||||
{
|
||||
shapedData[i].TryAdd("Rating", companiesAvgRatings[i]);
|
||||
shapedDataArray[i].TryAdd("Rating", companiesAvgRatings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (true, null, shapedData, pagingMetadata);
|
||||
var shapedData = shapedDataArray.AsQueryable();
|
||||
var pagingMetadata = ApplyPaging(ref shapedData, parameters.PageNumber,
|
||||
parameters.PageSize);
|
||||
shapedDataArray = shapedData.ToArray();
|
||||
|
||||
return (true, null, shapedDataArray, pagingMetadata);
|
||||
}
|
||||
|
||||
// Popularity is measured in number of routes using the station
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations)>
|
||||
GetPopularStations(int amount)
|
||||
// Popularity is measured in number tickets in which the address is the first or last station
|
||||
public async Task<(bool IsSucceed, string? message, IEnumerable<ExpandoObject> stations,
|
||||
PagingMetadata<ExpandoObject> pagingMetadata)>
|
||||
GetPopularStations(PopularAddressesParameters parameters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// throw new NotImplementedException();
|
||||
|
||||
var fromDateUtc =
|
||||
DateTime.UtcNow - TimeSpan.FromDays(parameters.Days ?? parameters.DefaultDays);
|
||||
|
||||
var dbTicketGroupsArray = await _dbContext.TicketGroups
|
||||
.Include(tg => tg.Tickets)
|
||||
.Where(tg => tg.Tickets.First().PurchaseDateTimeUtc >= fromDateUtc)
|
||||
.ToArrayAsync();
|
||||
|
||||
// Count appearances for each address id <Id, Count>
|
||||
var addressCountDict = new Dictionary<int, int>();
|
||||
|
||||
foreach (var tg in dbTicketGroupsArray)
|
||||
{
|
||||
if (!addressCountDict.ContainsKey(tg.Tickets.First().FirstRouteAddressId))
|
||||
{
|
||||
addressCountDict.Add(tg.Tickets.First().FirstRouteAddressId, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
addressCountDict[tg.Tickets.First().FirstRouteAddressId] += 1;
|
||||
}
|
||||
|
||||
if (!addressCountDict.ContainsKey(tg.Tickets.Last().LastRouteAddressId))
|
||||
{
|
||||
addressCountDict.Add(tg.Tickets.Last().LastRouteAddressId, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
addressCountDict[tg.Tickets.Last().LastRouteAddressId] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by number of appearances in descending order ->
|
||||
// Take amount given in parameters ->
|
||||
// Order by Id in Ascending order (needed for further sorting of two arrays simultaneously)
|
||||
addressCountDict = addressCountDict.OrderByDescending(a => a.Value)
|
||||
.Take(parameters.Amount).OrderBy(a => a.Key)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
// Separate Ids and counts into two arrays
|
||||
var addressIds = addressCountDict.Keys.ToArray();
|
||||
var addressCountArray = addressCountDict.Values.ToArray();
|
||||
|
||||
// Get top addresses from database ordered by Id (same as
|
||||
// addressIds addressCountDict and )
|
||||
var dbAddressesArray = await _dbContext.Addresses
|
||||
.Where(a => addressIds.Any(id => a.Id == id))
|
||||
.OrderBy(a => a.Id).ToArrayAsync();
|
||||
|
||||
// Sort addressCountArray and simultaneously sort dbAddressesArray
|
||||
// in the same manner
|
||||
int n = addressCountArray.Length;
|
||||
for (int i = 0; i < n - 1; i++)
|
||||
{
|
||||
for (int j = 0; j < n - i - 1; j++)
|
||||
{
|
||||
if (addressCountArray[j] > addressCountArray[j + 1])
|
||||
{
|
||||
// swap temp and arr[i]
|
||||
(addressCountArray[j], addressCountArray[j + 1]) = (addressCountArray[j + 1], addressCountArray[j]);
|
||||
(dbAddressesArray[j], dbAddressesArray[j + 1]) = (dbAddressesArray[j + 1], dbAddressesArray[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse sorted arrays (the result will be two "linked" arrays sorterd
|
||||
// in descending order by addressCount)
|
||||
addressCountArray = addressCountArray.Reverse().ToArray();
|
||||
dbAddressesArray = dbAddressesArray.Reverse().ToArray();
|
||||
|
||||
var addressDtos =
|
||||
_mapper.ProjectTo<AddressDto>(dbAddressesArray.AsQueryable());
|
||||
var shapedDataArray = _addressDataShaper.ShapeData(addressDtos,
|
||||
parameters.Fields ?? parameters.DefaultFields).ToArray();
|
||||
|
||||
if (parameters.Fields != null &&
|
||||
parameters.Fields.ToLower().Contains("purchaseCount".ToLower()))
|
||||
{
|
||||
for (int i = 0; i < shapedDataArray.Length; i++)
|
||||
{
|
||||
shapedDataArray[i].TryAdd("purchaseCount", addressCountArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var shapedData = shapedDataArray.AsQueryable();
|
||||
var pagingMetadata = ApplyPaging(ref shapedData, parameters.PageNumber,
|
||||
parameters.PageSize);
|
||||
shapedDataArray = shapedData.ToArray();
|
||||
|
||||
return (true, null, shapedDataArray, pagingMetadata);
|
||||
}
|
||||
|
||||
PagingMetadata<T> ApplyPaging<T>(ref IQueryable<T> obj,
|
||||
|
@ -92,16 +92,17 @@ public class TicketManagementService : ITicketManagementService
|
||||
tickets = tickets.Where(t => t.IsReturned == isReturned);
|
||||
}
|
||||
|
||||
// TODO: change TicketParameters
|
||||
void FilterByTicketUserId(ref IQueryable<Ticket> tickets,
|
||||
string? userId)
|
||||
{
|
||||
if (!tickets.Any() || String.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tickets = tickets.Where(t =>
|
||||
t.UserId.ToLower().Contains(userId.ToLower()));
|
||||
// if (!tickets.Any() || String.IsNullOrWhiteSpace(userId))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// tickets = tickets.Where(t =>
|
||||
// t.UserId.ToLower().Contains(userId.ToLower()));
|
||||
}
|
||||
|
||||
PagingMetadata<Ticket> ApplyPaging(ref IQueryable<Ticket> tickets,
|
||||
|
@ -10,7 +10,7 @@ public class PagingMetadata<T>
|
||||
public bool HasPrevious => CurrentPage > 1;
|
||||
public bool HasNext => CurrentPage < TotalPages;
|
||||
|
||||
public PagingMetadata(IQueryable<T> source, int pageNumber, int pageSize)
|
||||
public PagingMetadata(IEnumerable<T> source, int pageNumber, int pageSize)
|
||||
{
|
||||
TotalCount = source.Count();
|
||||
PageSize = pageSize;
|
||||
|
@ -2,7 +2,7 @@ namespace SharedModels.QueryParameters.Statistics;
|
||||
|
||||
public class EngagedUserParameters : ParametersBase
|
||||
{
|
||||
public readonly string DefaultFields = "id,firstName,lastName,username,email,phoneNumber,ticketCount";
|
||||
public readonly string DefaultFields = "id,firstName,lastName,username,email,phoneNumber,purchaseCount";
|
||||
public readonly int DefaultDays = 60;
|
||||
|
||||
public EngagedUserParameters()
|
||||
|
@ -0,0 +1,14 @@
|
||||
namespace SharedModels.QueryParameters.Statistics;
|
||||
|
||||
public class PopularAddressesParameters : ParametersBase
|
||||
{
|
||||
public readonly string DefaultFields = "id,name,purchaseCount";
|
||||
public readonly int DefaultDays = 60;
|
||||
|
||||
public PopularAddressesParameters()
|
||||
{
|
||||
Fields = DefaultFields;
|
||||
}
|
||||
|
||||
public int? Days { get; set; }
|
||||
}
|
Loading…
Reference in New Issue
Block a user