add command update name,email add health query and dal queryable
This commit is contained in:
parent
1de67205e0
commit
f8829cd8bd
@ -9,7 +9,7 @@ public class AppModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
//services.AddModule<DalModule>();
|
||||
services.AddModule<DalModule>();
|
||||
services.AddModule<SharedModule>();
|
||||
services.AddModule<CommandModule>();
|
||||
services.AddModule<QueryModule>();
|
||||
|
@ -39,14 +39,16 @@ builder.Services.AddDefaultCommandDiscovery();
|
||||
builder.Services.AddDefaultQueryDiscovery();
|
||||
builder.Services.AddFluentValidation();
|
||||
builder.Services.AddModule<AppModule>();
|
||||
|
||||
builder.Services.AddDefaultCommandDiscovery();
|
||||
builder.Services.AddDefaultQueryDiscovery();
|
||||
|
||||
if (builder.Configuration.GetValue<bool>("Swagger:Enable"))
|
||||
{
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Plan B Route Service Api", Version = "0.1.0" });
|
||||
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Constellation Heating Api", Version = "0.1.0" });
|
||||
|
||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||
{
|
||||
@ -105,6 +107,7 @@ mvcBuilder
|
||||
mvcBuilder
|
||||
.AddOpenHarborQueries()
|
||||
.AddOpenHarborDynamicQueries();
|
||||
|
||||
var connectionString = builder.Configuration.GetSection("Database").GetValue<string>("ConnectionString");
|
||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
||||
|
||||
@ -112,13 +115,7 @@ var dataSource = dataSourceBuilder.Build();
|
||||
|
||||
builder.Services.AddDbContextPool<CHDbContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(dataSource, o =>
|
||||
{
|
||||
// todo: ef 9.0+
|
||||
// o.MapEnum<RouteFileType>("route_file_type");
|
||||
});
|
||||
|
||||
|
||||
options.UseNpgsql(dataSource);
|
||||
if (builder.Configuration.GetValue<bool>("Debug"))
|
||||
{
|
||||
AppContext.SetSwitch("Npgsql.EnableConnectionStringLogging", true);
|
||||
|
@ -21,7 +21,7 @@
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7184;http://localhost:5274",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
|
@ -1,22 +1,18 @@
|
||||
using CH.Dal;
|
||||
using CH.Dal.DbEntity;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.Authority.Services;
|
||||
public class UserIdentityService
|
||||
{
|
||||
private User? _user;
|
||||
private User? user;
|
||||
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly CHDbContext _dbContext;
|
||||
private readonly IHttpContextAccessor httpContextAccessor;
|
||||
private readonly IConfiguration configuration;
|
||||
private readonly CHDbContext dbContext;
|
||||
|
||||
public string? SubjectId { get; }
|
||||
public string? Email { get; }
|
||||
@ -28,9 +24,9 @@ public class UserIdentityService
|
||||
public UserIdentityService(CHDbContext dbContext, IHttpContextAccessor httpContextAccessor,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_configuration = configuration;
|
||||
this.dbContext = dbContext;
|
||||
this.httpContextAccessor = httpContextAccessor;
|
||||
this.configuration = configuration;
|
||||
|
||||
// microsoft you twats! NameIdentifier = Sub
|
||||
SubjectId = ResolveClaim(ClaimTypes.NameIdentifier);
|
||||
@ -48,7 +44,7 @@ public class UserIdentityService
|
||||
|
||||
public bool IsAuthenticated()
|
||||
{
|
||||
var isAuthenticated = _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||||
var isAuthenticated = httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||||
|
||||
if (!isAuthenticated || null == EmailVerified)
|
||||
return false;
|
||||
@ -58,11 +54,11 @@ public class UserIdentityService
|
||||
|
||||
private string? ResolveClaim(params string[] name)
|
||||
{
|
||||
var isAuthenticated = _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||||
var isAuthenticated = httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
||||
if (false == isAuthenticated)
|
||||
return null;
|
||||
|
||||
var claim = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(claim => name.Contains(claim.Type));
|
||||
var claim = httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(claim => name.Contains(claim.Type));
|
||||
return claim?.Value;
|
||||
}
|
||||
public async Task<User?> GetUserOrDefaultAsync(CancellationToken cancellationToken = default)
|
||||
@ -70,26 +66,23 @@ public class UserIdentityService
|
||||
if (false == IsAuthenticated())
|
||||
return null;
|
||||
|
||||
if (null != _user)
|
||||
return _user;
|
||||
if (null != user)
|
||||
return user;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Email))
|
||||
return null;
|
||||
|
||||
// otherwise search for the user by email
|
||||
var email = Email?.ToLower() ?? "";
|
||||
|
||||
//_user = await _dbContext.Users
|
||||
// .Include(user => user.SubjectId)
|
||||
// .FirstOrDefaultAsync(user => user.Email.ToLower() == email, cancellationToken);
|
||||
user = await dbContext.Users
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(user => user.SubjectId == SubjectId, cancellationToken);
|
||||
|
||||
|
||||
// create user if it doesn't exist
|
||||
if (null == _user)
|
||||
{
|
||||
_user = await CreateUserAsync(cancellationToken);
|
||||
return _user;
|
||||
}
|
||||
return _user;
|
||||
if (null == user)
|
||||
user = await CreateUserAsync(cancellationToken);
|
||||
|
||||
return user;
|
||||
}
|
||||
public async Task<bool> IsAuthorizedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@ -100,12 +93,17 @@ public class UserIdentityService
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
Email = Email,
|
||||
FirstName = FirstName,
|
||||
LastName = LastName,
|
||||
Email = Email,
|
||||
SubjectId = SubjectId,
|
||||
Verified = EmailVerified ?? false,
|
||||
};
|
||||
|
||||
//await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
|
||||
dbContext.Users.Add(user);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
<PackageReference Include="OpenHarbor.Storage.S3" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Command\User\UpdateUserName\" />
|
||||
<ProjectReference Include="..\CH.Authority\CH.Authority.csproj" />
|
||||
<ProjectReference Include="..\CH.Dal\CH.Dal.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
14
CH.CQRS/Command/User/ServiceCollectionExtension.cs
Normal file
14
CH.CQRS/Command/User/ServiceCollectionExtension.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.FluentValidation;
|
||||
|
||||
namespace CH.CQRS.Command.User;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddUserCommand(this IServiceCollection services)
|
||||
{
|
||||
services.AddCommand<UpdateNameCommand, UpdateNameCommandHandler, UpdateNameCommandValidator>();
|
||||
services.AddCommand<UpdateEmailCommand, UpdateEmailCommandHandler, UpdateEmailCommandValidator>();
|
||||
return services;
|
||||
}
|
||||
}
|
46
CH.CQRS/Command/User/UpdateEmailCommand.cs
Normal file
46
CH.CQRS/Command/User/UpdateEmailCommand.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using CH.CQRS.Service.User;
|
||||
using CH.CQRS.Service.User.Options;
|
||||
using CH.Dal;
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CQRS.Command.User;
|
||||
|
||||
public class UpdateEmailCommand
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateEmailCommandHandler(UserService userService) : ICommandHandler<UpdateEmailCommand>
|
||||
{
|
||||
public Task HandleAsync(UpdateEmailCommand command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return userService.UpdateEmailAsync(new UpdateEmailCommandOptions
|
||||
{
|
||||
Email = command.Email
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateEmailCommandValidator : AbstractValidator<UpdateEmailCommand>
|
||||
{
|
||||
public UpdateEmailCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.Email)
|
||||
.NotEmpty()
|
||||
.EmailAddress()
|
||||
.MustAsync(async (email, CancellationToken) =>
|
||||
{
|
||||
var emailComparaison = email.Trim().ToLower();
|
||||
var emailInUse = await dbContext.Users.AnyAsync(user => user.Email.ToLower() == emailComparaison);
|
||||
return false == emailInUse;
|
||||
})
|
||||
.WithMessage("This email is already in use by another user.");
|
||||
}
|
||||
}
|
35
CH.CQRS/Command/User/UpdateNameCommand.cs
Normal file
35
CH.CQRS/Command/User/UpdateNameCommand.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using CH.CQRS.Service.User;
|
||||
using CH.CQRS.Service.User.Options;
|
||||
using CH.Dal;
|
||||
using FluentValidation;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CQRS.Command.User;
|
||||
|
||||
public class UpdateNameCommand
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
}
|
||||
public class UpdateNameCommandHandler(UserService userService) : ICommandHandler<UpdateNameCommand>
|
||||
{
|
||||
public Task HandleAsync(UpdateNameCommand command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return userService.UpdateNameAsync(new UpdateNameCommandOptions
|
||||
{
|
||||
FirstName = command.FirstName,
|
||||
LastName = command.LastName,
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
public class UpdateNameCommandValidator : AbstractValidator<UpdateNameCommand>
|
||||
{
|
||||
public UpdateNameCommandValidator()
|
||||
{
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CH.CQRS.Command.User;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -11,7 +12,7 @@ public class CommandModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
|
||||
services.AddUserCommand();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
51
CH.CQRS/Query/Health/HealthQuery.cs
Normal file
51
CH.CQRS/Query/Health/HealthQuery.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using CH.Dal;
|
||||
namespace CH.CQRS.Query.Health;
|
||||
public class HealthQuery
|
||||
{
|
||||
|
||||
}
|
||||
public class HealthQueryHandler(CHDbContext dbContext, IMemoryCache memoryCache) : IQueryHandler<HealthQuery, HealthQueryResult>
|
||||
{
|
||||
private const string HealthCheckCacheKey = "HealthStatus";
|
||||
private const int CacheDurationInSeconds = 60;
|
||||
public async Task<HealthQueryResult> HandleAsync(HealthQuery query, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
if (memoryCache.TryGetValue(HealthCheckCacheKey, out var memory))
|
||||
{
|
||||
return (HealthQueryResult)memory;
|
||||
}
|
||||
var (databaseStatus, databaseLatency) = await MeasureLatencyAsync(CheckDatabaseHealthAsync);
|
||||
|
||||
var healthCheckResult = new HealthQueryResult
|
||||
{
|
||||
ApiStatus = true,
|
||||
DatabaseStatus = databaseStatus,
|
||||
DatabaseLatency = $"{databaseLatency}ms",
|
||||
};
|
||||
memoryCache.Set(HealthCheckCacheKey, healthCheckResult, TimeSpan.FromSeconds(CacheDurationInSeconds));
|
||||
return healthCheckResult;
|
||||
}
|
||||
public static async Task<(bool isHealthy, long latencyMs)> MeasureLatencyAsync(Func<Task<bool>> healthCheckAction)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var isHealthy = await healthCheckAction();
|
||||
stopwatch.Stop();
|
||||
return (isHealthy, stopwatch.ElapsedMilliseconds);
|
||||
}
|
||||
private async Task<bool> CheckDatabaseHealthAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await dbContext.Database.ExecuteSqlRawAsync("SELECT 1");
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
14
CH.CQRS/Query/Health/HealthQueryResult.cs
Normal file
14
CH.CQRS/Query/Health/HealthQueryResult.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CQRS.Query.Health;
|
||||
|
||||
public class HealthQueryResult
|
||||
{
|
||||
public bool ApiStatus { get; set; }
|
||||
public bool DatabaseStatus { get; set; }
|
||||
public string? DatabaseLatency { get; set; }
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CH.CQRS.Query.Health;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -11,7 +13,7 @@ public class QueryModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
|
||||
services.AddQuery<HealthQuery, HealthQueryResult, HealthQueryHandler>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
12
CH.CQRS/Service/User/Options/UpdateEmailCommandOptions.cs
Normal file
12
CH.CQRS/Service/User/Options/UpdateEmailCommandOptions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CQRS.Service.User.Options;
|
||||
|
||||
public class UpdateEmailCommandOptions
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
}
|
13
CH.CQRS/Service/User/Options/UpdateNameCommandOptions.cs
Normal file
13
CH.CQRS/Service/User/Options/UpdateNameCommandOptions.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CQRS.Service.User.Options;
|
||||
|
||||
public class UpdateNameCommandOptions
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
}
|
44
CH.CQRS/Service/User/UserService.cs
Normal file
44
CH.CQRS/Service/User/UserService.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using CH.Authority.Services;
|
||||
using CH.CQRS.Service.User.Options;
|
||||
using CH.Dal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
|
||||
|
||||
namespace CH.CQRS.Service.User;
|
||||
|
||||
public class UserService(CHDbContext dbContext, UserIdentityService identityService)
|
||||
{
|
||||
public async Task UpdateEmailAsync(UpdateEmailCommandOptions options,CancellationToken cancellationToken)
|
||||
{
|
||||
// todo: change on oauth server first, make sure to persist changes if db save fail after keycloak api is succesful
|
||||
var user = await dbContext.Users.FirstOrDefaultAsync(user => user.SubjectId == identityService.SubjectId, cancellationToken);
|
||||
if (null != user)
|
||||
{
|
||||
if (user.Email != options.Email)
|
||||
{
|
||||
user.Email = options.Email;
|
||||
}
|
||||
dbContext.Users.Update(user);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
return;
|
||||
}
|
||||
public async Task UpdateNameAsync(UpdateNameCommandOptions options,CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await dbContext.Users.FirstOrDefaultAsync( user => user.SubjectId == identityService.SubjectId, cancellationToken);
|
||||
if(null != user)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(options.FirstName) && user.FirstName != options.FirstName)
|
||||
{
|
||||
user.FirstName = options.FirstName;
|
||||
}
|
||||
if (!String.IsNullOrWhiteSpace(options.LastName) && user.LastName != options.LastName)
|
||||
{
|
||||
user.LastName = options.LastName;
|
||||
}
|
||||
dbContext.Users.Update(user);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CH.CQRS.Service.User;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
|
||||
namespace CH.CQRS;
|
||||
@ -6,6 +7,7 @@ public class SharedModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<UserService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,9 @@ public class CHDbContext(DbContextOptions options) : CHDbScaffoldedContext(optio
|
||||
{
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
return;
|
||||
|
@ -34,9 +34,13 @@ public partial class CHDbScaffoldedContext : DbContext
|
||||
entity.ToTable("location");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'::text)")
|
||||
.HasColumnName("created_at");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Machine>(entity =>
|
||||
@ -49,11 +53,15 @@ public partial class CHDbScaffoldedContext : DbContext
|
||||
entity.Property(e => e.Address)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("address");
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'::text)")
|
||||
.HasColumnName("created_at");
|
||||
entity.Property(e => e.LocationId).HasColumnName("location_id");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.Status).HasColumnName("status");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
entity.Property(e => e.UserId).HasColumnName("user_id");
|
||||
|
||||
entity.HasOne(d => d.Location).WithMany(p => p.Machines)
|
||||
@ -67,19 +75,28 @@ public partial class CHDbScaffoldedContext : DbContext
|
||||
|
||||
modelBuilder.Entity<User>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("user_pkey");
|
||||
entity.HasKey(e => e.Id).HasName("users_pkey");
|
||||
|
||||
entity.ToTable("user");
|
||||
entity.ToTable("users");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'::text)")
|
||||
.HasColumnName("created_at");
|
||||
entity.Property(e => e.Email)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("email");
|
||||
entity.Property(e => e.FirstName).HasMaxLength(255);
|
||||
entity.Property(e => e.LastName).HasMaxLength(255);
|
||||
entity.Property(e => e.FirstName)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("first_name");
|
||||
entity.Property(e => e.LastName)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("last_name");
|
||||
entity.Property(e => e.SubjectId)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("subject_id");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
entity.Property(e => e.Verified).HasColumnName("verified");
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
|
@ -8,6 +8,7 @@ public class DalModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
return services;
|
||||
return services.AddSingleton<IAsyncQueryableHandlerService, InMemoryQueryableHandlerService>()
|
||||
.AddTransient(typeof(IQueryableProvider<>), typeof(DefaultQueryableProvider<>));
|
||||
}
|
||||
}
|
@ -9,5 +9,9 @@ public partial class Location
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual ICollection<Machine> Machines { get; set; } = new List<Machine>();
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ public partial class Machine
|
||||
|
||||
public long? UserId { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual Location? Location { get; set; }
|
||||
|
||||
public virtual User? User { get; set; }
|
||||
|
@ -15,5 +15,11 @@ public partial class User
|
||||
|
||||
public string SubjectId { get; set; } = null!;
|
||||
|
||||
public bool Verified { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual ICollection<Machine> Machines { get; set; } = new List<Machine>();
|
||||
}
|
||||
|
19
CH.Dal/DefaultQueryableProvider.cs
Normal file
19
CH.Dal/DefaultQueryableProvider.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.Dal;
|
||||
public class DefaultQueryableProvider<TEntity>(CHDbContext dbContext, IServiceProvider serviceProvider) : IQueryableProvider<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
public Task<IQueryable<TEntity>> GetQueryableAsync(object query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (serviceProvider.GetService(typeof(IQueryableProviderOverride<TEntity>)) is IQueryableProviderOverride<TEntity> queryableProviderOverride)
|
||||
return queryableProviderOverride.GetQueryableAsync(query, cancellationToken);
|
||||
|
||||
return Task.FromResult(dbContext.Set<TEntity>().AsQueryable());
|
||||
}
|
||||
}
|
11
CH.Dal/IQueryableProviderOverride.cs
Normal file
11
CH.Dal/IQueryableProviderOverride.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.Dal;
|
||||
public interface IQueryableProviderOverride<T>
|
||||
{
|
||||
Task<IQueryable<T>> GetQueryableAsync(object query, CancellationToken cancellationToken = default);
|
||||
}
|
52
CH.Dal/InMemoryQueryableHandlerService.cs
Normal file
52
CH.Dal/InMemoryQueryableHandlerService.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using PoweredSoft.Data.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.Dal;
|
||||
public class InMemoryQueryableHandlerService : IAsyncQueryableHandlerService
|
||||
{
|
||||
public Task<bool> AnyAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.Any(predicate));
|
||||
}
|
||||
|
||||
public Task<bool> AnyAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.Any());
|
||||
}
|
||||
|
||||
public bool CanHandle<T>(IQueryable<T> queryable)
|
||||
{
|
||||
var result = queryable is EnumerableQuery<T>;
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<int> CountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.Count());
|
||||
}
|
||||
|
||||
public Task<T> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.FirstOrDefault());
|
||||
}
|
||||
|
||||
public Task<T> FirstOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.FirstOrDefault(predicate));
|
||||
}
|
||||
|
||||
public Task<long> LongCountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.LongCount());
|
||||
}
|
||||
|
||||
public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(queryable.ToList());
|
||||
}
|
||||
}
|
16
CH.Dal/ServiceCollectionExtensions.cs
Normal file
16
CH.Dal/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.Dal;
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddQueryableProviderOverride<T, TService>(this IServiceCollection services)
|
||||
where TService : class, IQueryableProviderOverride<T>
|
||||
{
|
||||
return services.AddTransient<IQueryableProviderOverride<T>, TService>();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user