Compare commits
9 Commits
main
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
06d88a02ae | |||
8348dd5e84 | |||
feeaf06330 | |||
fbe26898fa | |||
44dceee7b8 | |||
8bed1d39f3 | |||
29f0614e56 | |||
4f28a73af6 | |||
457e72bc95 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@
|
||||
##
|
||||
## Get latest from `dotnet new gitignore`
|
||||
|
||||
appsettings*.json
|
||||
!appsettings.Example.json
|
||||
|
||||
# dotenv files
|
||||
.env
|
||||
|
||||
|
@ -17,16 +17,6 @@ public class GetAddressesPageQueryAuthorizer :
|
||||
|
||||
public override void BuildPolicy(GetAddressesPageQuery request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||
{
|
||||
RequiredRoles =
|
||||
[IdentityRole.Administrator, IdentityRole.CompanyOwner],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
UseRequirement(new AllowAllRequirement());
|
||||
}
|
||||
}
|
||||
|
@ -141,11 +141,18 @@ public class SearchAllQueryHandler :
|
||||
// Find paths
|
||||
|
||||
var departureAddress = routeAddressDetails
|
||||
.First(e => e.RouteAddress.Address.Guid == request.DepartureAddressGuid)
|
||||
.RouteAddress.Address;
|
||||
.FirstOrDefault(
|
||||
e => e.RouteAddress.Address.Guid == request.DepartureAddressGuid)
|
||||
?.RouteAddress.Address;
|
||||
var arrivalAddress = routeAddressDetails
|
||||
.First(e => e.RouteAddress.Address.Guid == request.ArrivalAddressGuid)
|
||||
.RouteAddress.Address;
|
||||
.FirstOrDefault(
|
||||
e => e.RouteAddress.Address.Guid == request.ArrivalAddressGuid)
|
||||
?.RouteAddress.Address;
|
||||
|
||||
if (departureAddress == null || arrivalAddress == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
|
||||
var paths = new List<List<TaggedEdge<Address, RouteAddressDetail>>>();
|
||||
|
@ -19,8 +19,8 @@ public static class Configuration
|
||||
Environment.GetEnvironmentVariable("TravelGuide_Environment");
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile($"./appsettings.{environment}.json", optional: true)
|
||||
.AddJsonFile($"./appsettings.json", optional: true)
|
||||
.AddJsonFile($"./appsettings.{environment}.json", optional: true)
|
||||
.AddEnvironmentVariables(prefix: "TravelGuide_")
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using cuqmbr.TravelGuide.Persistence.PostgreSql;
|
||||
using cuqmbr.TravelGuide.Persistence.InMemory;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Configuration.Persistence;
|
||||
|
||||
@ -76,10 +77,33 @@ public static class Configuration
|
||||
$"{configuration.Type} datastore is not supported.");
|
||||
}
|
||||
|
||||
if (configuration.Seed)
|
||||
if (configuration.SeedIdentity || configuration.SeedData)
|
||||
{
|
||||
using var serviceProvider = services.BuildServiceProvider();
|
||||
DbSeeder.Seed(serviceProvider);
|
||||
|
||||
var unitOfWork =
|
||||
serviceProvider.GetRequiredService<UnitOfWork>();
|
||||
var passwordHasher =
|
||||
serviceProvider.GetRequiredService<PasswordHasherService>();
|
||||
|
||||
using var dbSeeder = new DbSeeder(unitOfWork, passwordHasher);
|
||||
|
||||
if (configuration.SeedIdentity)
|
||||
{
|
||||
dbSeeder.SeedIdentity();
|
||||
}
|
||||
|
||||
// Data can not be seeded without seeding identity
|
||||
if (configuration.SeedData && !configuration.SeedIdentity)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Can not seed data without seeding identity.");
|
||||
}
|
||||
|
||||
if (configuration.SeedData)
|
||||
{
|
||||
dbSeeder.SeedData();
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
|
@ -18,8 +18,10 @@ public sealed class AspNetSessionUserService : SessionUserService
|
||||
get
|
||||
{
|
||||
var claimValue = _httpContext.User.Claims
|
||||
.FirstOrDefault(c => c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Sub))
|
||||
.FirstOrDefault(c =>
|
||||
c.Type == JwtRegisteredClaimNames.Sub ||
|
||||
c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Sub))
|
||||
?.Value;
|
||||
|
||||
var parsed = System.Guid.TryParse(claimValue, out var guid);
|
||||
@ -29,18 +31,24 @@ public sealed class AspNetSessionUserService : SessionUserService
|
||||
}
|
||||
|
||||
public string? Username => _httpContext.User.Claims
|
||||
.FirstOrDefault(c => c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Nickname))
|
||||
.FirstOrDefault(c =>
|
||||
c.Type == JwtRegisteredClaimNames.Nickname ||
|
||||
c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Nickname))
|
||||
?.Value;
|
||||
|
||||
public string? Email => _httpContext.User.Claims
|
||||
.FirstOrDefault(c => c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Email))
|
||||
.FirstOrDefault(c =>
|
||||
c.Type == JwtRegisteredClaimNames.Email ||
|
||||
c.Properties
|
||||
.Any(p => p.Value == JwtRegisteredClaimNames.Email))
|
||||
?.Value;
|
||||
|
||||
public ICollection<IdentityRole> Roles => _httpContext.User.Claims
|
||||
.Where(c => c.Properties
|
||||
.Any(p => p.Value == "roles"))
|
||||
.Where(c =>
|
||||
c.Type == "roles" ||
|
||||
c.Properties
|
||||
.Any(p => p.Value == "roles"))
|
||||
.Select(c => IdentityRole.FromName(c.Value))
|
||||
.ToArray() ?? default!;
|
||||
|
||||
|
@ -7,18 +7,22 @@
|
||||
},
|
||||
"Localization": {
|
||||
"DefaultCultureName": "en-US",
|
||||
"CacheDuration": "00:30:00"
|
||||
"CacheDuration": "00:00:30"
|
||||
},
|
||||
"JsonWebToken": {
|
||||
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
||||
"Audience": "https://travel-guide.cuqmbr.xyz",
|
||||
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
||||
"AccessTokenValidity": "24:00:00",
|
||||
"RefreshTokenValidity": "72:00:00"
|
||||
"IssuerSigningKey": "change-this-sufficiently-logn-string",
|
||||
"AccessTokenValidity": "00:03:00",
|
||||
"RefreshTokenValidity": "14.00:00:00"
|
||||
},
|
||||
"Datastore": {
|
||||
"Type": "postgresql",
|
||||
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
||||
"ConnectionString": "Host=127.0.0.1;Port=5432;Username=postgres;Password=0000;Database=travel_guide;",
|
||||
"PartitionName": "application",
|
||||
"Migrate": true,
|
||||
"SeedIdentity": true,
|
||||
"SeedData": false
|
||||
},
|
||||
"PaymentProcessing": {
|
||||
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
@ -1,42 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"Type": "SimpleConsole",
|
||||
"LogLevel": "Information",
|
||||
"TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK",
|
||||
"UseUtcTimestamp": true
|
||||
},
|
||||
"Localization": {
|
||||
"DefaultCultureName": "en-US",
|
||||
"CacheDuration": "00:30:00"
|
||||
},
|
||||
"JsonWebToken": {
|
||||
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
||||
"Audience": "https://travel-guide.cuqmbr.xyz",
|
||||
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
||||
"AccessTokenValidity": "24:00:00",
|
||||
"RefreshTokenValidity": "72:00:00"
|
||||
},
|
||||
"Datastore": {
|
||||
"Type": "postgresql",
|
||||
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
||||
},
|
||||
"PaymentProcessing": {
|
||||
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
||||
"ResultAddressBase": "https://travel-guide.cuqmbr.xyz",
|
||||
"LiqPay": {
|
||||
"PublicKey": "sandbox_xxxxxxxxxxxx",
|
||||
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
},
|
||||
"Email": {
|
||||
"Smtp": {
|
||||
"Host": "mail.travel-guide.cuqmbr.xyz",
|
||||
"Port": "465",
|
||||
"UseTls": true,
|
||||
"Username": "no-reply",
|
||||
"Password": "super-secret-password",
|
||||
"SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
|
||||
"SenderName": "Travel Guide"
|
||||
}
|
||||
}
|
||||
}
|
@ -12,5 +12,7 @@ public sealed class ConfigurationOptions
|
||||
|
||||
public bool Migrate { get; set; } = true;
|
||||
|
||||
public bool Seed { get; set; } = false;
|
||||
public bool SeedIdentity { get; set; } = true;
|
||||
|
||||
public bool SeedData { get; set; } = false;
|
||||
}
|
||||
|
@ -4,103 +4,847 @@ using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence;
|
||||
|
||||
public static class DbSeeder
|
||||
public sealed class DbSeeder : IDisposable
|
||||
{
|
||||
public static void Seed(IServiceProvider serviceProvider)
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
|
||||
private readonly PasswordHasherService _passwordHasher;
|
||||
|
||||
public DbSeeder(UnitOfWork unitOfWork, PasswordHasherService passwordHasher)
|
||||
{
|
||||
var unitOfWork =
|
||||
serviceProvider.GetRequiredService<UnitOfWork>();
|
||||
|
||||
var passwordHasher =
|
||||
serviceProvider.GetRequiredService<PasswordHasherService>();
|
||||
|
||||
_unitOfWork = unitOfWork;
|
||||
_passwordHasher = passwordHasher;
|
||||
}
|
||||
|
||||
public void SeedIdentity()
|
||||
{
|
||||
// Seed Roles
|
||||
|
||||
var datastoreRoles = _unitOfWork.RoleRepository
|
||||
.GetPageAsync(1, IdentityRole.Enumerations.Count,
|
||||
CancellationToken.None)
|
||||
.Result.Items.Select(r => r.Value);
|
||||
|
||||
var identityRoles = IdentityRole.Enumerations.Select(r => r.Value);
|
||||
|
||||
foreach (var role in identityRoles)
|
||||
{
|
||||
var datastoreRoles = unitOfWork.RoleRepository
|
||||
.GetPageAsync(1, IdentityRole.Enumerations.Count,
|
||||
CancellationToken.None)
|
||||
.Result.Items.Select(r => r.Value);
|
||||
|
||||
var roles = IdentityRole.Enumerations.Select(r => r.Value);
|
||||
|
||||
foreach (var role in roles)
|
||||
if (datastoreRoles.Contains(role))
|
||||
{
|
||||
|
||||
if (datastoreRoles.Contains(role))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
unitOfWork.RoleRepository.AddOneAsync(
|
||||
new Role() { Value = role },
|
||||
CancellationToken.None).Wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
unitOfWork.SaveAsync(CancellationToken.None).Wait();
|
||||
_unitOfWork.RoleRepository.AddOneAsync(
|
||||
new Role() { Value = role },
|
||||
CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
_unitOfWork.SaveAsync(CancellationToken.None).Wait();
|
||||
|
||||
|
||||
// Seed Accounts
|
||||
|
||||
var isAccountsPresent =
|
||||
_unitOfWork.AccountRepository.GetPageAsync(
|
||||
1, 1, CancellationToken.None)
|
||||
.Result.Items.Any();
|
||||
|
||||
if (isAccountsPresent)
|
||||
{
|
||||
var accounts =
|
||||
new (string Username, string Email,
|
||||
string Password, IdentityRole[] Roles)[]
|
||||
{
|
||||
("admin", "admin", "admin",
|
||||
new [] { IdentityRole.Administrator }),
|
||||
};
|
||||
|
||||
var roles = unitOfWork.RoleRepository
|
||||
.GetPageAsync(1, IdentityRole.Enumerations.Count,
|
||||
CancellationToken.None)
|
||||
.Result.Items;
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var datastoreAccount =
|
||||
unitOfWork.AccountRepository.GetOneAsync(
|
||||
e => e.Email == account.Email, CancellationToken.None)
|
||||
.Result;
|
||||
|
||||
if (datastoreAccount != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var password = Encoding.UTF8.GetBytes(account.Password);
|
||||
|
||||
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||
var hash = passwordHasher
|
||||
.HashAsync(password, salt, CancellationToken.None)
|
||||
.Result;
|
||||
|
||||
var saltBase64 = Convert.ToBase64String(salt);
|
||||
var hashBase64 = Convert.ToBase64String(hash);
|
||||
|
||||
unitOfWork.AccountRepository.AddOneAsync(
|
||||
new Account()
|
||||
{
|
||||
Username = account.Username,
|
||||
Email = account.Email,
|
||||
PasswordHash = hashBase64,
|
||||
PasswordSalt = saltBase64,
|
||||
AccountRoles = account.Roles.Select(ar =>
|
||||
new AccountRole()
|
||||
{
|
||||
RoleId = roles.Single(dr => dr.Value.Equals(ar)).Id
|
||||
})
|
||||
.ToArray()
|
||||
},
|
||||
CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
unitOfWork.SaveAsync(CancellationToken.None).Wait();
|
||||
return;
|
||||
}
|
||||
|
||||
unitOfWork.Dispose();
|
||||
var accounts =
|
||||
new (string Username, string Email, IdentityRole[] Roles)[]
|
||||
{
|
||||
("admin", "admin@email.com",
|
||||
new [] { IdentityRole.Administrator }),
|
||||
};
|
||||
|
||||
var roles = _unitOfWork.RoleRepository
|
||||
.GetPageAsync(1, IdentityRole.Enumerations.Count,
|
||||
CancellationToken.None)
|
||||
.Result.Items;
|
||||
|
||||
var datastoreAccounts =
|
||||
_unitOfWork.AccountRepository.GetPageAsync(
|
||||
e => accounts.Select(a => a.Email).Contains(e.Email),
|
||||
1, accounts.Count(), CancellationToken.None)
|
||||
.Result.Items;
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var datastoreAccount = datastoreAccounts
|
||||
.SingleOrDefault(a => a.Email == account.Email);
|
||||
|
||||
if (datastoreAccount != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var password = Encoding.UTF8.GetBytes(account.Username);
|
||||
|
||||
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||
var hash = _passwordHasher
|
||||
.HashAsync(password, salt, CancellationToken.None)
|
||||
.Result;
|
||||
|
||||
var saltBase64 = Convert.ToBase64String(salt);
|
||||
var hashBase64 = Convert.ToBase64String(hash);
|
||||
|
||||
_unitOfWork.AccountRepository.AddOneAsync(
|
||||
new Account()
|
||||
{
|
||||
Username = account.Username,
|
||||
Email = account.Email,
|
||||
PasswordHash = hashBase64,
|
||||
PasswordSalt = saltBase64,
|
||||
AccountRoles = account.Roles.Select(ar =>
|
||||
new AccountRole()
|
||||
{
|
||||
RoleId = roles.Single(dr => dr.Value.Equals(ar)).Id
|
||||
})
|
||||
.ToArray()
|
||||
},
|
||||
CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
_unitOfWork.Save();
|
||||
}
|
||||
|
||||
public void SeedData()
|
||||
{
|
||||
var isAddressesPresent =
|
||||
_unitOfWork.AddressRepository.GetPageAsync(
|
||||
1, 1, CancellationToken.None)
|
||||
.Result.Items.Any();
|
||||
|
||||
if (isAddressesPresent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var countries = new Country[]
|
||||
{
|
||||
new Country()
|
||||
{
|
||||
Name = "Ukraine"
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var country in countries)
|
||||
{
|
||||
_unitOfWork.CountryRepository
|
||||
.AddOneAsync(country, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var regions = new Region[]
|
||||
{
|
||||
new Region()
|
||||
{
|
||||
Name = "Kyiv Region",
|
||||
Country = countries[0]
|
||||
},
|
||||
new Region()
|
||||
{
|
||||
Name = "Zhytomyr Region",
|
||||
Country = countries[0]
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
_unitOfWork.RegionRepository
|
||||
.AddOneAsync(region, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var cities = new City[]
|
||||
{
|
||||
new City()
|
||||
{
|
||||
Name = "Brovary",
|
||||
Region = regions[0]
|
||||
},
|
||||
new City()
|
||||
{
|
||||
Name = "Bila Tserkva",
|
||||
Region = regions[0]
|
||||
},
|
||||
new City()
|
||||
{
|
||||
Name = "Korostyshiv",
|
||||
Region = regions[1]
|
||||
},
|
||||
new City()
|
||||
{
|
||||
Name = "Stanyshivka",
|
||||
Region = regions[1]
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var city in cities)
|
||||
{
|
||||
_unitOfWork.CityRepository
|
||||
.AddOneAsync(city, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var addresses = new Address[]
|
||||
{
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 0",
|
||||
Longitude = 0,
|
||||
Latitude = 0,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[0]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 1",
|
||||
Longitude = 1,
|
||||
Latitude = 1,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[0]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 2",
|
||||
Longitude = 2,
|
||||
Latitude = 2,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[0]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 3",
|
||||
Longitude = 3,
|
||||
Latitude = 3,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[1]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 4",
|
||||
Longitude = 4,
|
||||
Latitude = 4,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[1]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 5",
|
||||
Longitude = 5,
|
||||
Latitude = 5,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[1]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 6",
|
||||
Longitude = 6,
|
||||
Latitude = 6,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[2]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 7",
|
||||
Longitude = 7,
|
||||
Latitude = 7,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[2]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 8",
|
||||
Longitude = 8,
|
||||
Latitude = 8,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[3]
|
||||
},
|
||||
new Address()
|
||||
{
|
||||
Name = "Address 9",
|
||||
Longitude = 9,
|
||||
Latitude = 9,
|
||||
VehicleType = VehicleType.Bus,
|
||||
City = cities[3]
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
_unitOfWork.AddressRepository
|
||||
.AddOneAsync(address, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var routes = new Route[]
|
||||
{
|
||||
new Route()
|
||||
{
|
||||
Name = "Route 0",
|
||||
VehicleType = VehicleType.Bus,
|
||||
RouteAddresses = new RouteAddress[]
|
||||
{
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 0,
|
||||
Address = addresses[3]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 1,
|
||||
Address = addresses[6]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 2,
|
||||
Address = addresses[2]
|
||||
}
|
||||
}
|
||||
},
|
||||
new Route()
|
||||
{
|
||||
Name = "Route 1",
|
||||
VehicleType = VehicleType.Bus,
|
||||
RouteAddresses = new RouteAddress[]
|
||||
{
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 0,
|
||||
Address = addresses[1]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 1,
|
||||
Address = addresses[2]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 2,
|
||||
Address = addresses[7]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 3,
|
||||
Address = addresses[9]
|
||||
}
|
||||
}
|
||||
},
|
||||
new Route()
|
||||
{
|
||||
Name = "Route 2",
|
||||
VehicleType = VehicleType.Bus,
|
||||
RouteAddresses = new RouteAddress[]
|
||||
{
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 0,
|
||||
Address = addresses[0]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 1,
|
||||
Address = addresses[1]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 2,
|
||||
Address = addresses[3]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 3,
|
||||
Address = addresses[4]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 4,
|
||||
Address = addresses[7]
|
||||
},
|
||||
new RouteAddress()
|
||||
{
|
||||
Order = 5,
|
||||
Address = addresses[8]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var route in routes)
|
||||
{
|
||||
_unitOfWork.RouteRepository
|
||||
.AddOneAsync(route, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var roles = _unitOfWork.RoleRepository
|
||||
.GetPageAsync(1, IdentityRole.Enumerations.Count,
|
||||
CancellationToken.None)
|
||||
.Result.Items;
|
||||
|
||||
var companyOwnerRoleId = roles
|
||||
.SingleOrDefault(
|
||||
e => e.Value == IdentityRole.CompanyOwner)
|
||||
?.Id;
|
||||
|
||||
if (companyOwnerRoleId == null)
|
||||
{
|
||||
throw new InvalidOperationException("Can not find required role.");
|
||||
}
|
||||
|
||||
var companyOwnerAccountCredentials =
|
||||
new (string Username, string Email)[]
|
||||
{
|
||||
("light_travel_owner", "light_travel_owner0@email.com")
|
||||
};
|
||||
|
||||
var companyOwnerAccounts = new List<Account>();
|
||||
|
||||
foreach (var accountCredential in companyOwnerAccountCredentials)
|
||||
{
|
||||
var password = Encoding.UTF8.GetBytes(accountCredential.Username);
|
||||
|
||||
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||
var hash = _passwordHasher
|
||||
.HashAsync(password, salt, CancellationToken.None)
|
||||
.Result;
|
||||
|
||||
var saltBase64 = Convert.ToBase64String(salt);
|
||||
var hashBase64 = Convert.ToBase64String(hash);
|
||||
|
||||
companyOwnerAccounts.Add(new Account()
|
||||
{
|
||||
Username = accountCredential.Username,
|
||||
Email = accountCredential.Email,
|
||||
PasswordHash = hashBase64,
|
||||
PasswordSalt = saltBase64,
|
||||
AccountRoles = new AccountRole[]
|
||||
{
|
||||
new AccountRole()
|
||||
{
|
||||
RoleId = (long)companyOwnerRoleId
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var account in companyOwnerAccounts)
|
||||
{
|
||||
_unitOfWork.AccountRepository
|
||||
.AddOneAsync(account, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
var companies = new Company[]
|
||||
{
|
||||
new Company()
|
||||
{
|
||||
Name = "Light Travel",
|
||||
LegalAddress = "Ukraine, Kyiv Region, Kyiv, Address 0",
|
||||
ContactEmail = "light-travel@email.com",
|
||||
ContactPhoneNumber = "+38000000000",
|
||||
Account = companyOwnerAccounts[0]
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var company in companies)
|
||||
{
|
||||
_unitOfWork.CompanyRepository
|
||||
.AddOneAsync(company, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var companyEmployeeRoleId = roles
|
||||
.SingleOrDefault(
|
||||
e => e.Value == IdentityRole.CompanyEmployee)
|
||||
?.Id;
|
||||
|
||||
if (companyEmployeeRoleId == null)
|
||||
{
|
||||
throw new InvalidOperationException("Can not find required role.");
|
||||
}
|
||||
|
||||
var companyEmployeeAccountCredentials =
|
||||
new (string Username, string Email)[]
|
||||
{
|
||||
("company_employee0", "company_employee0@email.com"),
|
||||
("company_employee1", "company_employee1@email.com"),
|
||||
("company_employee2", "company_employee2@email.com")
|
||||
};
|
||||
|
||||
var companyEmployeeAccounts = new List<Account>();
|
||||
var employees = new List<Employee>();
|
||||
|
||||
foreach (var accountCredential in companyEmployeeAccountCredentials)
|
||||
{
|
||||
var password = Encoding.UTF8.GetBytes(accountCredential.Username);
|
||||
|
||||
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||
var hash = _passwordHasher
|
||||
.HashAsync(password, salt, CancellationToken.None)
|
||||
.Result;
|
||||
|
||||
var saltBase64 = Convert.ToBase64String(salt);
|
||||
var hashBase64 = Convert.ToBase64String(hash);
|
||||
|
||||
companyEmployeeAccounts.Add(new Account()
|
||||
{
|
||||
Username = accountCredential.Username,
|
||||
Email = accountCredential.Email,
|
||||
PasswordHash = hashBase64,
|
||||
PasswordSalt = saltBase64,
|
||||
AccountRoles = new AccountRole[]
|
||||
{
|
||||
new AccountRole()
|
||||
{
|
||||
RoleId = (long)companyEmployeeRoleId
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var account in companyEmployeeAccounts)
|
||||
{
|
||||
_unitOfWork.AccountRepository
|
||||
.AddOneAsync(account, CancellationToken.None)
|
||||
.Wait();
|
||||
|
||||
employees.Add(new Employee()
|
||||
{
|
||||
FirstName = $"{account.Username}'s fname",
|
||||
LastName = $"{account.Username}'s lname",
|
||||
Patronymic = $"{account.Username}'s patronymic",
|
||||
Sex =
|
||||
DateTimeOffset.UtcNow.Ticks % 2 == 1 ?
|
||||
Sex.Male : Sex.Female,
|
||||
BirthDate = DateOnly.FromDateTime(
|
||||
DateTimeOffset.UtcNow.Subtract(
|
||||
TimeSpan.FromDays(365 * 20)).Date),
|
||||
Company = companies[0],
|
||||
Documents = new EmployeeDocument[]
|
||||
{
|
||||
new EmployeeDocument()
|
||||
{
|
||||
DocumentType = DocumentType.Passport,
|
||||
Information = $"{account.Username}'s passport"
|
||||
}
|
||||
},
|
||||
Account = account
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var employee in employees)
|
||||
{
|
||||
_unitOfWork.EmployeeRepository
|
||||
.AddOneAsync(employee, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
var vehicles = new Vehicle[]
|
||||
{
|
||||
new Bus()
|
||||
{
|
||||
Number = "XX0000XX",
|
||||
Model = "Model 0",
|
||||
Capacity = 30,
|
||||
Company = companies[0]
|
||||
},
|
||||
new Bus()
|
||||
{
|
||||
Number = "XX1111XX",
|
||||
Model = "Model 1",
|
||||
Capacity = 30,
|
||||
Company = companies[0]
|
||||
},
|
||||
new Bus()
|
||||
{
|
||||
Number = "XX2222XX",
|
||||
Model = "Model 2",
|
||||
Capacity = 30,
|
||||
Company = companies[0]
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
_unitOfWork.VehicleRepository
|
||||
.AddOneAsync(vehicle, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
// Vehicle Enrollments (binding between a vehicle and a route,
|
||||
// specifying departure date and time)
|
||||
// and
|
||||
// Route Address Details (for earch address in the route specifying
|
||||
// cost and time of travel from current to next address
|
||||
// and current address stop duration)
|
||||
|
||||
// VE1: 4 -> 7 -> 3
|
||||
// \
|
||||
// VE2: 2 -> 3 -> 8 -> 10
|
||||
// \
|
||||
// VE3: 1 -> 2 -> 4 -> 5 -> 8 -> 9
|
||||
// ---------------------------------> time
|
||||
|
||||
// Vehicle Enrollment 1
|
||||
//
|
||||
// Route: 4 -> 7 -> 3
|
||||
// Departure Time: 2025-01-01 07:30:00.000
|
||||
// Time to Next Address:
|
||||
// 4: P0000-00-00T00:30:00.000
|
||||
// 7: P0000-00-00T00:30:00.000
|
||||
// 3: P0000-00-00T00:00:00.000
|
||||
// Total time moving: 60 minutes
|
||||
// Cost to Next Address:
|
||||
// 4: 5
|
||||
// 7: 11.50
|
||||
// 3: 0
|
||||
// Total: 16.50
|
||||
// Address Stop duration:
|
||||
// 4: P0000-00-00T00:00:00.000
|
||||
// 7: P0000-00-00T00:10:00.000
|
||||
// 3: P0000-00-00T00:00:00.000
|
||||
// Total time in stops: 10 minutes
|
||||
// Total time on road: 70 minutes
|
||||
//
|
||||
// Arriave to 3 at 8:40
|
||||
|
||||
// Vehicle Enrollment 2
|
||||
//
|
||||
// Route: 2 -> 3 -> 8 -> 10
|
||||
// Departure Time: 2025-01-01 08:30:00.000
|
||||
// Time to Next Address:
|
||||
// 2: P0000-00-00T00:20:00.000
|
||||
// 3: P0000-00-00T00:30:00.000
|
||||
// 8: P0000-00-00T00:20:00.000
|
||||
// 10: P0000-00-00T00:00:00.000
|
||||
// Total time moving: 70 minutes
|
||||
// Cost to Next Address:
|
||||
// 2: 4.30
|
||||
// 3: 14.71
|
||||
// 8: 12.10
|
||||
// 10: 0
|
||||
// Total: 21.11
|
||||
// Address Stop duration:
|
||||
// 2: P0000-00-00T00:00:00.000
|
||||
// 3: P0000-00-00T00:10:00.000
|
||||
// 8: P0000-00-00T00:10:00.000
|
||||
// 10: P0000-00-00T00:00:00.000
|
||||
// Total time in stops: 20 minutes
|
||||
// Total time on road: 90 minutes
|
||||
//
|
||||
// Arrive to 3 at 8:50
|
||||
// Arrive to 8 at 9:30
|
||||
|
||||
// Vehicle Enrollment 3
|
||||
//
|
||||
// Route: 1 -> 2 -> 4 -> 5 -> 8 -> 9
|
||||
// Departure Time: 2025-01-01 07:30:00.000
|
||||
// Time to Next Address:
|
||||
// 1: P0000-00-00T00:30:00.000
|
||||
// 2: P0000-00-00T00:20:00.000
|
||||
// 4: P0000-00-00T00:30:00.000
|
||||
// 5: P0000-00-00T00:30:00.000
|
||||
// 8: P0000-00-00T00:30:00.000
|
||||
// 9: P0000-00-00T00:00:00.000
|
||||
// Total time moving: 140 minutes
|
||||
// Cost to Next Address:
|
||||
// 1: 5
|
||||
// 2: 10
|
||||
// 4: 11.2
|
||||
// 5: 5.23
|
||||
// 8: 5.2
|
||||
// 9: 0
|
||||
// Total: 36.63
|
||||
// Address Stop duration:
|
||||
// 1: P0000-00-00T00:00:00.000
|
||||
// 2: P0000-00-00T00:10:00.000
|
||||
// 4: P0000-00-00T00:10:00.000
|
||||
// 5: P0000-00-00T00:10:00.000
|
||||
// 8: P0000-00-00T00:10:00.000
|
||||
// 9: P0000-00-00T00:00:00.000
|
||||
// Total time in stops: 40 minutes
|
||||
// Total time on road: 180 minutes
|
||||
//
|
||||
// Arrive to 8 at 9:40
|
||||
|
||||
var vehicleEnrollments = new VehicleEnrollment[]
|
||||
{
|
||||
new VehicleEnrollment()
|
||||
{
|
||||
DepartureTime = DateTimeOffset.UtcNow.AddDays(1),
|
||||
Currency = Currency.EUR,
|
||||
Vehicle = vehicles[0],
|
||||
Route = routes[0],
|
||||
RouteAddressDetails = new RouteAddressDetail[]
|
||||
{
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 5,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[0].RouteAddresses.ElementAt(0)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 11.5M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[0].RouteAddresses.ElementAt(1)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.Zero,
|
||||
CostToNextAddress = 0,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[0].RouteAddresses.ElementAt(2)
|
||||
}
|
||||
},
|
||||
VehicleEnrollmentEmployees = new VehicleEnrollmentEmployee[]
|
||||
{
|
||||
new VehicleEnrollmentEmployee()
|
||||
{
|
||||
Employee = employees[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
new VehicleEnrollment()
|
||||
{
|
||||
DepartureTime = DateTimeOffset.UtcNow.AddDays(1).AddHours(1),
|
||||
Currency = Currency.UAH,
|
||||
Vehicle = vehicles[1],
|
||||
Route = routes[1],
|
||||
RouteAddressDetails = new RouteAddressDetail[]
|
||||
{
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(20),
|
||||
CostToNextAddress = 4.3M,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[1].RouteAddresses.ElementAt(0)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 14.71M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[1].RouteAddresses.ElementAt(1)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(20),
|
||||
CostToNextAddress = 12.1M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[1].RouteAddresses.ElementAt(2)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.Zero,
|
||||
CostToNextAddress = 0,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[1].RouteAddresses.ElementAt(3)
|
||||
}
|
||||
},
|
||||
VehicleEnrollmentEmployees = new VehicleEnrollmentEmployee[]
|
||||
{
|
||||
new VehicleEnrollmentEmployee()
|
||||
{
|
||||
Employee = employees[1]
|
||||
}
|
||||
}
|
||||
},
|
||||
new VehicleEnrollment()
|
||||
{
|
||||
DepartureTime = DateTimeOffset.UtcNow.AddDays(1),
|
||||
Currency = Currency.USD,
|
||||
Vehicle = vehicles[2],
|
||||
Route = routes[2],
|
||||
RouteAddressDetails = new RouteAddressDetail[]
|
||||
{
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 5,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(0)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(20),
|
||||
CostToNextAddress = 10,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(1)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 11.2M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(2)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 5.23M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(3)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.FromMinutes(30),
|
||||
CostToNextAddress = 5.2M,
|
||||
CurrentAddressStopTime = TimeSpan.FromMinutes(10),
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(4)
|
||||
},
|
||||
new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = TimeSpan.Zero,
|
||||
CostToNextAddress = 0,
|
||||
CurrentAddressStopTime = TimeSpan.Zero,
|
||||
RouteAddress = routes[2].RouteAddresses.ElementAt(5)
|
||||
}
|
||||
},
|
||||
VehicleEnrollmentEmployees = new VehicleEnrollmentEmployee[]
|
||||
{
|
||||
new VehicleEnrollmentEmployee()
|
||||
{
|
||||
Employee = employees[2]
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var enrollment in vehicleEnrollments)
|
||||
{
|
||||
_unitOfWork.VehicleEnrollmentRepository
|
||||
.AddOneAsync(enrollment, CancellationToken.None)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.Save();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_unitOfWork.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using Moq;
|
||||
using System.Globalization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using cuqmbr.TravelGuide.Configuration.Infrastructure;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.IntegrationTests;
|
||||
|
||||
@ -29,9 +30,8 @@ public abstract class TestBase : IDisposable
|
||||
})
|
||||
.ConfigureLogging()
|
||||
.ConfigureApplication()
|
||||
.ConfigureInfrastructure()
|
||||
.ConfigurePersistence();
|
||||
// TODO: Create InMemory configuration for Identity
|
||||
// .ConfigureIdentity();
|
||||
|
||||
SetCulture("en-US");
|
||||
SetTimeZone("Europe/Kyiv");
|
||||
|
Loading…
Reference in New Issue
Block a user