From 1451314d56009346c363dd74dcdca7fbad258c80 Mon Sep 17 00:00:00 2001 From: DavidGudEnough Date: Mon, 13 Jan 2025 19:19:03 -0500 Subject: [PATCH] add validations to command --- .../CryptoStats/GetCryptoStatsCommand.cs | 2 + .../Energy/CreateEnergyProviderCommand.cs | 16 +++- .../Command/Energy/CreateEnergyRateCommand.cs | 22 ++++- .../CreateEnergyRateExceptionCommand.cs | 24 ++++- .../Energy/DisableEnergyProviderCommand.cs | 10 +- .../Energy/DisableEnergyRateCommand.cs | 9 +- .../Energy/EnableEnergyProviderCommand.cs | 9 +- .../Command/Energy/EnableEnergyRateCommand.cs | 15 +++ .../Energy/ServiceCollectionExtension.cs | 12 ++- .../Energy/UpdateEnergyProviderCommand.cs | 19 +++- .../Command/Energy/UpdateEnergyRateCommand.cs | 36 +++++-- .../UpdateEnergyRateExceptionCommand.cs | 18 +++- CH.CQRS/Service/Energy/EnergyService.cs | 96 +++++++++++++------ .../Options/CreateEnergyRateCommandOptions.cs | 3 +- ...CreateEnergyRateExceptionCommandOptions.cs | 4 +- .../Options/UpdateEnergyRateCommandOptions.cs | 6 +- ...UpdateEnergyRateExceptionCommandOptions.cs | 6 +- CH.Dal.Abstractions/Class1.cs | 7 -- CH.Dal.Abstractions/IHasId.cs | 6 ++ CH.Dal/CH.Dal.csproj | 1 + CH.Dal/CHDbScaffoldedContext.cs | 7 +- CH.Dal/DbEntity/EnergyProvider.Extensions.cs | 8 ++ CH.Dal/DbEntity/EnergyRate.Extension.cs | 5 +- .../DbEntity/EnergyRateException.Extension.cs | 5 +- CH.Dal/DbEntity/EnergyRateUpdate.cs | 4 +- CH.Dal/Validators/DbEntityExistValidator.cs | 25 +++++ CH.KeycloakApi/KeycloakService.cs | 52 +++++----- CH.KeycloakApi/KeycloakSettings.cs | 4 +- CH.KeycloakApi/KeycloakUser.cs | 14 ++- ConstellationHeating.sln | 6 ++ 30 files changed, 334 insertions(+), 117 deletions(-) delete mode 100644 CH.Dal.Abstractions/Class1.cs create mode 100644 CH.Dal.Abstractions/IHasId.cs create mode 100644 CH.Dal/DbEntity/EnergyProvider.Extensions.cs create mode 100644 CH.Dal/Validators/DbEntityExistValidator.cs diff --git a/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs b/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs index 8092c36..414e7ba 100644 --- a/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs +++ b/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs @@ -13,6 +13,7 @@ namespace CH.CQRS.Command.CryptoStats; public class GetCryptoStatsCommand { } + public class GetCryptoStatsCommandHandler(CryptoService cryptoService) : ICommandHandler { public Task HandleAsync(GetCryptoStatsCommand command, CancellationToken cancellationToken = default) @@ -20,6 +21,7 @@ public class GetCryptoStatsCommandHandler(CryptoService cryptoService) : IComman return cryptoService.GetCryptoStatsAsync(cancellationToken); } } + public class GetCryptoStatsCommandValidator : AbstractValidator { public GetCryptoStatsCommandValidator() diff --git a/CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs b/CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs index 4244093..55aeabb 100644 --- a/CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs +++ b/CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs @@ -1,6 +1,10 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -24,8 +28,16 @@ public class CreateEnergyProviderCommandHandler(EnergyService energyService) : I public class CreateEnergyProviderCommandValidator : AbstractValidator { - public CreateEnergyProviderCommandValidator() + public CreateEnergyProviderCommandValidator(CHDbContext dbContext) { - + RuleFor(command => command.Name) + .NotEmpty() + .MinimumLength(3) + .MustAsync(async (name, cancellationToken) => + { + var nameInUse = await dbContext.EnergyProviders.AnyAsync(energyProvider => energyProvider.Name == name, cancellationToken); + return false == nameInUse; + }) + .WithMessage("This Name is already in use by another energy provider."); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs b/CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs index 4847144..d274010 100644 --- a/CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs +++ b/CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs @@ -1,6 +1,11 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; +using CH.Enum; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -10,7 +15,7 @@ public class CreateEnergyRateCommand public long ProviderId { get; set; } public required string Name { get; set; } public decimal Price { get; set; } - public required string? Currency { get; set; } + public Currency Currency { get; set; } public bool Active { get; set; } } public class CreateEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler @@ -30,10 +35,21 @@ public class CreateEnergyRateCommandHandler(EnergyService energyService) : IComm public class CreateEnergyRateCommandValidator : AbstractValidator { - public CreateEnergyRateCommandValidator() + public CreateEnergyRateCommandValidator(CHDbContext dbContext) { + RuleFor(command => command.Name) + .NotEmpty() + .MinimumLength(3) + .MustAsync(async (name, cancellationToken) => + { + var nameInUse = await dbContext.EnergyRates.AnyAsync(energyRate => energyRate.Name == name, cancellationToken); + return false == nameInUse; + }) + .WithMessage("This Name is already in use by another energy rate."); RuleFor(command => command.Price).GreaterThanOrEqualTo(0); RuleFor(command => command.ProviderId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided provider Id is invalid."); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs b/CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs index f1fe389..551bd14 100644 --- a/CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs +++ b/CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs @@ -1,6 +1,11 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; +using CH.Enum; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -10,7 +15,7 @@ public class CreateEnergyRateExceptionCommand public long RateId { get; set; } public required string Name { get; set; } public decimal EnergyThreshold { get; set; } - public string? ResetType { get; set; } + public EnergyRateExceptionThresholdResetType ResetType { get; set; } public DateTime? StartedAt { get; set; } public DateTime? EndedAt { get; set; } } @@ -33,11 +38,24 @@ public class CreateEnergyRateExceptionCommandHandler(EnergyService energyService } public class CreateEnergyRateExceptionCommandValidator : AbstractValidator { - public CreateEnergyRateExceptionCommandValidator() + public CreateEnergyRateExceptionCommandValidator(CHDbContext dbContext) { + RuleFor(command => command.Name) + .NotEmpty() + .MinimumLength(3) + .MustAsync(async (name, cancellationToken) => + { + var nameInUse = await dbContext.EnergyRateExceptions.AnyAsync(energyRateException => energyRateException.Name == name, cancellationToken); + return false == nameInUse; + }) + .WithMessage("This Name is already in use by another energy rate exception."); + RuleFor(command => command.EnergyThreshold) .GreaterThan(0); + RuleFor(command => command.RateId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided Rate Id is invalid."); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs b/CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs index 684a98d..47b12bf 100644 --- a/CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs +++ b/CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs @@ -1,5 +1,8 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; using OpenHarbor.CQRS.Abstractions; @@ -24,9 +27,12 @@ public class DisableEnergyProviderCommandHandler(EnergyService energyService) : public class DisableEnergyProviderCommandValidator : AbstractValidator { - public DisableEnergyProviderCommandValidator() + public DisableEnergyProviderCommandValidator(CHDbContext dbContext) { RuleFor(command => command.ProviderId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided provider Id is invalid."); + } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs b/CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs index d231b0c..7ed683a 100644 --- a/CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs +++ b/CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs @@ -1,5 +1,8 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; using OpenHarbor.CQRS.Abstractions; @@ -24,9 +27,11 @@ public class DisableEnergyRateCommandHandler(EnergyService energyService) : ICom public class DisableEnergyRateCommandValidator : AbstractValidator { - public DisableEnergyRateCommandValidator() + public DisableEnergyRateCommandValidator(CHDbContext dbContext) { RuleFor(command => command.RateId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided Rate Id is invalid."); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs b/CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs index 77fd40b..1e4fdaa 100644 --- a/CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs +++ b/CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs @@ -1,5 +1,8 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; using OpenHarbor.CQRS.Abstractions; @@ -22,8 +25,12 @@ public class EnableEnergyProviderCommandHandler(EnergyService energyService) : I public class EnableEnergyProviderCommandValidator : AbstractValidator { - public EnableEnergyProviderCommandValidator() + public EnableEnergyProviderCommandValidator(CHDbContext dbContext) { + RuleFor(command => command.ProviderId) + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided provider Id is invalid."); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs b/CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs index ed1bc00..ad9aa4c 100644 --- a/CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs +++ b/CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs @@ -1,4 +1,8 @@ using CH.CQRS.Service.Energy; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; +using FluentValidation; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -16,4 +20,15 @@ public class EnableEnergyRateCommandHandler(EnergyService energyService) : IComm RateId = command.RateId }, cancellationToken); } +} + +public class EnableEnergyRateCommandValidator : AbstractValidator +{ + public EnableEnergyRateCommandValidator(CHDbContext dbContext) + { + RuleFor(command => command.RateId) + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided Rate Id is invalid."); + } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/ServiceCollectionExtension.cs b/CH.CQRS/Command/Energy/ServiceCollectionExtension.cs index 7944ebf..69e031c 100644 --- a/CH.CQRS/Command/Energy/ServiceCollectionExtension.cs +++ b/CH.CQRS/Command/Energy/ServiceCollectionExtension.cs @@ -10,18 +10,24 @@ public static class ServiceCollectionExtension services .AddCommand(); - services - .AddCommand(); services .AddCommand(); + services + .AddCommand(); services .AddCommand(); services .AddCommand(); + services + .AddCommand(); + services + .AddCommand(); services .AddCommand(); diff --git a/CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs b/CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs index 6b93fd7..3bf9c33 100644 --- a/CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs +++ b/CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs @@ -1,6 +1,10 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -24,9 +28,20 @@ public class UpdateEnergyProviderCommandHandler(EnergyService energyService) : I } public class UpdateEnergyProviderCommandValidator : AbstractValidator { - public UpdateEnergyProviderCommandValidator() + public UpdateEnergyProviderCommandValidator(CHDbContext dbContext) { + RuleFor(command => command.Name) + .NotEmpty() + .MinimumLength(3) + .MustAsync(async (name, cancellationToken) => + { + var nameInUse = await dbContext.EnergyProviders.AnyAsync(energyProvider => energyProvider.Name == name, cancellationToken); + return false == nameInUse; + }) + .WithMessage("This Name is already in use by another energy provider."); + RuleFor(command => command.ProviderId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs b/CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs index 18cb192..59ef1f9 100644 --- a/CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs +++ b/CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs @@ -1,6 +1,11 @@ +using System.Data; using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -8,11 +13,9 @@ namespace CH.CQRS.Command.Energy; public class UpdateEnergyRateCommand { public long RateId { get; set; } - public bool SendAlert { get; set; } - public required DateTime StartedAt { get; set; } - public DateTime? AppliedAt { get; set; } + public string? Name { get; set; } + public DateTime? StartedAt { get; set; } public decimal? Rate { get; set; } - public DateTime? UpdatedAt { get; set; } } public class UpdateEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler { @@ -21,19 +24,34 @@ public class UpdateEnergyRateCommandHandler(EnergyService energyService) : IComm return energyService.UpdateEnergyRateAsync(new UpdateEnergyRateCommandOptions { RateId = command.RateId, + Name = command.Name, StartedAt = command.StartedAt, - AppliedAt = command.AppliedAt, - Rate = command.Rate, - SendAlert = command.SendAlert, - UpdatedAt = command.UpdatedAt + Rate = command.Rate }, cancellationToken); } } public class UpdateEnergyRateCommandValidator : AbstractValidator { - public UpdateEnergyRateCommandValidator() + public UpdateEnergyRateCommandValidator(CHDbContext dbContext) { + RuleFor(command => command.Name) + .NotEmpty() + .MinimumLength(3) + .When(command => false == String.IsNullOrWhiteSpace(command.Name)) + .MustAsync(async (name, cancellationToken) => + { + var nameInUse = await dbContext.EnergyRates.AnyAsync(energyRate => energyRate.Name == name, cancellationToken); + return false == nameInUse; + }) + .WithMessage("This Name is already in use by another energy rate."); + RuleFor(command => command.RateId) + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)) + .WithMessage("The provided Rate Id is invalid."); + + RuleFor(command => command.Rate) + .GreaterThanOrEqualTo(0); } } \ No newline at end of file diff --git a/CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs b/CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs index 5282949..dfb5c31 100644 --- a/CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs +++ b/CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs @@ -1,6 +1,11 @@ using CH.CQRS.Service.Energy; using CH.CQRS.Service.Energy.Options; +using CH.Dal; +using CH.Dal.DbEntity; +using CH.Dal.Validators; +using CH.Enum; using FluentValidation; +using Microsoft.EntityFrameworkCore; using OpenHarbor.CQRS.Abstractions; namespace CH.CQRS.Command.Energy; @@ -8,8 +13,8 @@ namespace CH.CQRS.Command.Energy; public class UpdateEnergyRateExceptionCommand { public long EnergyRateExceptionId { get; set; } - public decimal EnergyThreshold { get; set; } - public string? ResetType { get; set; } + public decimal? EnergyThreshold { get; set; } + public EnergyRateExceptionThresholdResetType ResetType { get; set; } public DateTime? StartedAt { get; set; } public DateTime? EndedAt { get; set; } } @@ -31,11 +36,14 @@ public class UpdateEnergyRateExceptionCommandHandler(EnergyService energyService public class UpdateEnergyRateExceptionCommandValidator : AbstractValidator { - public UpdateEnergyRateExceptionCommandValidator() + public UpdateEnergyRateExceptionCommandValidator(CHDbContext dbContext) { RuleFor(command => command.EnergyRateExceptionId) - .NotEmpty(); + .NotEmpty() + .SetValidator(new DbEntityExistValidator(dbContext)); + RuleFor(command => command.EnergyThreshold) - .GreaterThan(0); + .GreaterThan(0) + .When(command => command.EnergyThreshold.HasValue); } } \ No newline at end of file diff --git a/CH.CQRS/Service/Energy/EnergyService.cs b/CH.CQRS/Service/Energy/EnergyService.cs index 9dd6d6a..7764dc8 100644 --- a/CH.CQRS/Service/Energy/EnergyService.cs +++ b/CH.CQRS/Service/Energy/EnergyService.cs @@ -28,7 +28,7 @@ public class EnergyService(CHDbContext dbContext) var energyRate = new EnergyRate { Name = options.Name, - Currency = (Currency)System.Enum.Parse(typeof(Currency), options.Currency?.ToUpper()), + Currency = options.Currency, Price = options.Price, CreatedAt = DateTime.UtcNow, Provider = provider, @@ -51,8 +51,7 @@ public class EnergyService(CHDbContext dbContext) StartedAt = options.StartedAt, EndedAt = options.EndedAt, EnergyThreshold = options.EnergyThreshold, - ResetType = (EnergyRateExceptionThresholdResetType)System.Enum - .Parse(typeof(EnergyRateExceptionThresholdResetType), options.ResetType.ToLower() ?? ""), + ResetType = options.ResetType, }; await dbContext.AddAsync(energyRateException, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); @@ -61,29 +60,54 @@ public class EnergyService(CHDbContext dbContext) public async Task UpdateEnergyProviderAsync(UpdateEnergyProviderCommandOptions options, CancellationToken cancellationToken) { - var provider = await dbContext.EnergyProviders.FirstOrDefaultAsync(provider => provider.Id == options.ProviderId, cancellationToken); - if (null != provider) - { - provider.Name = options.Name; - provider.UpdatedAt = DateTime.UtcNow; - await dbContext.SaveChangesAsync(cancellationToken); - } - + var provider = await dbContext.EnergyProviders + .AsTracking() + .FirstOrDefaultAsync(provider => provider.Id == options.ProviderId, cancellationToken); + + if (provider is null) + return; + + provider.Name = options.Name; + provider.UpdatedAt = DateTime.UtcNow; + await dbContext.SaveChangesAsync(cancellationToken); } + public async Task UpdateEnergyRateValueAsync(UpdateEnergyRateCommandOptions options, CancellationToken cancellationToken) + { + var energyRate = await dbContext.EnergyRates + .AsTracking() + .FirstOrDefaultAsync(energyRate => energyRate.Id == options.RateId, cancellationToken); + + if (energyRate is null) + return; + + if (options.Rate.HasValue) + energyRate.Price = options.Rate.Value; + + if (false == string.IsNullOrWhiteSpace(options.Name)) + energyRate.Name = options.Name; + + energyRate.UpdatedAt = DateTime.UtcNow; + } public async Task UpdateEnergyRateAsync(UpdateEnergyRateCommandOptions options, CancellationToken cancellationToken) { - var energyRateUpdate = new EnergyRateUpdate + if (options.StartedAt.HasValue) { - RateId = options.RateId, - Rate = options.Rate, - StartedAt = options.StartedAt, - CreatedAt = DateTime.UtcNow, - UpdatedAt = options.UpdatedAt, - AppliedAt = options.AppliedAt, - SendAlert = options.SendAlert, - }; - await dbContext.AddAsync(energyRateUpdate, cancellationToken); + var energyRateUpdate = new EnergyRateUpdate + { + RateId = options.RateId, + Rate = options.Rate, + Name = options.Name ?? "", + StartedAt = options.StartedAt.Value, + }; + + await dbContext.AddAsync(energyRateUpdate, cancellationToken); + } + else + { + await UpdateEnergyRateValueAsync(options, cancellationToken); + } + await dbContext.SaveChangesAsync(cancellationToken); } @@ -91,19 +115,29 @@ public class EnergyService(CHDbContext dbContext) CancellationToken cancellationToken) { var energyRateException = await dbContext.EnergyRateExceptions + .AsTracking() .FirstOrDefaultAsync(rateException => rateException.Id == options.EnergyRateExceptionId, cancellationToken); - if (null != energyRateException) - { + + if (energyRateException is null) + return; + + if (options.EnergyThreshold.HasValue) energyRateException.EnergyThreshold = options.EnergyThreshold; + + if (options.StartedAt.HasValue) energyRateException.StartedAt = options.StartedAt; + + if (options.EndedAt.HasValue) energyRateException.EndedAt = options.EndedAt; - energyRateException.UpdatedAt = DateTime.UtcNow; - if (String.IsNullOrWhiteSpace(options.ResetType)) - { - energyRateException.ResetType = (EnergyRateExceptionThresholdResetType)System.Enum.Parse(typeof(EnergyRateExceptionThresholdResetType),options.ResetType) ; - } - await dbContext.SaveChangesAsync(cancellationToken); + + energyRateException.UpdatedAt = DateTime.UtcNow; + + if (energyRateException.ResetType != options.ResetType) + { + energyRateException.ResetType = options.ResetType; } + + await dbContext.SaveChangesAsync(cancellationToken); } public async Task DisableEnergyProviderAsync(DisableEnergyProviderCommandOptions options, @@ -113,7 +147,7 @@ public class EnergyService(CHDbContext dbContext) if (null != energyProvider) { energyProvider.Active = false; - energyProvider.DisabledAt = options.DisabledAt; + energyProvider.DisabledAt = options.DisabledAt ?? DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); } } @@ -124,7 +158,7 @@ public class EnergyService(CHDbContext dbContext) if (null != energyRate) { energyRate.Active = false; - energyRate.DisabledAt = options.DisabledAt; + energyRate.DisabledAt = options.DisabledAt ?? DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); } } diff --git a/CH.CQRS/Service/Energy/Options/CreateEnergyRateCommandOptions.cs b/CH.CQRS/Service/Energy/Options/CreateEnergyRateCommandOptions.cs index 32c8d00..3447ddc 100644 --- a/CH.CQRS/Service/Energy/Options/CreateEnergyRateCommandOptions.cs +++ b/CH.CQRS/Service/Energy/Options/CreateEnergyRateCommandOptions.cs @@ -1,4 +1,5 @@ using Amazon.Runtime.Internal.Transform; +using CH.Enum; namespace CH.CQRS.Service.Energy.Options; @@ -7,6 +8,6 @@ public class CreateEnergyRateCommandOptions public long ProviderId { get; set; } public required string Name { get; set; } public decimal? Price { get; set; } - public required string? Currency { get; set; } + public Currency Currency { get; set; } public bool Active { get; set; } } \ No newline at end of file diff --git a/CH.CQRS/Service/Energy/Options/CreateEnergyRateExceptionCommandOptions.cs b/CH.CQRS/Service/Energy/Options/CreateEnergyRateExceptionCommandOptions.cs index aae8a68..4f034ca 100644 --- a/CH.CQRS/Service/Energy/Options/CreateEnergyRateExceptionCommandOptions.cs +++ b/CH.CQRS/Service/Energy/Options/CreateEnergyRateExceptionCommandOptions.cs @@ -1,3 +1,5 @@ +using CH.Enum; + namespace CH.CQRS.Service.Energy.Options; public class CreateEnergyRateExceptionCommandOptions @@ -5,7 +7,7 @@ public class CreateEnergyRateExceptionCommandOptions public long RateId { get; set; } public required string Name { get; set; } public decimal EnergyThreshold { get; set; } - public string? ResetType { get; set; } + public EnergyRateExceptionThresholdResetType ResetType { get; set; } public DateTime? StartedAt { get; set; } public DateTime? EndedAt { get; set; } } \ No newline at end of file diff --git a/CH.CQRS/Service/Energy/Options/UpdateEnergyRateCommandOptions.cs b/CH.CQRS/Service/Energy/Options/UpdateEnergyRateCommandOptions.cs index 0e27825..5e6469f 100644 --- a/CH.CQRS/Service/Energy/Options/UpdateEnergyRateCommandOptions.cs +++ b/CH.CQRS/Service/Energy/Options/UpdateEnergyRateCommandOptions.cs @@ -3,9 +3,7 @@ namespace CH.CQRS.Service.Energy.Options; public class UpdateEnergyRateCommandOptions { public long RateId { get; set; } - public bool SendAlert { get; set; } - public required DateTime StartedAt { get; set; } - public DateTime? AppliedAt { get; set; } + public DateTime? StartedAt { get; set; } public decimal? Rate { get; set; } - public DateTime? UpdatedAt { get; set; } + public string? Name { get; set; } } \ No newline at end of file diff --git a/CH.CQRS/Service/Energy/Options/UpdateEnergyRateExceptionCommandOptions.cs b/CH.CQRS/Service/Energy/Options/UpdateEnergyRateExceptionCommandOptions.cs index 4b31bf6..218b336 100644 --- a/CH.CQRS/Service/Energy/Options/UpdateEnergyRateExceptionCommandOptions.cs +++ b/CH.CQRS/Service/Energy/Options/UpdateEnergyRateExceptionCommandOptions.cs @@ -1,10 +1,12 @@ +using CH.Enum; + namespace CH.CQRS.Service.Energy.Options; public class UpdateEnergyRateExceptionCommandOptions { public long EnergyRateExceptionId { get; set; } - public decimal EnergyThreshold { get; set; } - public string? ResetType { get; set; } + public decimal? EnergyThreshold { get; set; } + public EnergyRateExceptionThresholdResetType ResetType { get; set; } public DateTime? StartedAt { get; set; } public DateTime? EndedAt { get; set; } } \ No newline at end of file diff --git a/CH.Dal.Abstractions/Class1.cs b/CH.Dal.Abstractions/Class1.cs deleted file mode 100644 index 0021712..0000000 --- a/CH.Dal.Abstractions/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CH.Dal.Abstractions -{ - public class Class1 - { - - } -} diff --git a/CH.Dal.Abstractions/IHasId.cs b/CH.Dal.Abstractions/IHasId.cs new file mode 100644 index 0000000..27bac34 --- /dev/null +++ b/CH.Dal.Abstractions/IHasId.cs @@ -0,0 +1,6 @@ +namespace CH.Dal.Abstractions; + +public interface IHasId +{ + public T Id { get; set; } +} \ No newline at end of file diff --git a/CH.Dal/CH.Dal.csproj b/CH.Dal/CH.Dal.csproj index 953227d..4fb4b3b 100644 --- a/CH.Dal/CH.Dal.csproj +++ b/CH.Dal/CH.Dal.csproj @@ -18,6 +18,7 @@ + diff --git a/CH.Dal/CHDbScaffoldedContext.cs b/CH.Dal/CHDbScaffoldedContext.cs index deec810..c08baac 100644 --- a/CH.Dal/CHDbScaffoldedContext.cs +++ b/CH.Dal/CHDbScaffoldedContext.cs @@ -112,15 +112,14 @@ public partial class CHDbScaffoldedContext : DbContext entity.ToTable("energy_rate_update"); entity.Property(e => e.Id).HasColumnName("id"); - entity.Property(e => e.AppliedAt).HasColumnName("applied_at"); 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.Rate).HasColumnName("rate"); entity.Property(e => e.RateId).HasColumnName("rate_id"); - entity.Property(e => e.SendAlert) - .HasDefaultValue(false) - .HasColumnName("send_alert"); entity.Property(e => e.StartedAt).HasColumnName("started_at"); entity.Property(e => e.UpdatedAt).HasColumnName("updated_at"); diff --git a/CH.Dal/DbEntity/EnergyProvider.Extensions.cs b/CH.Dal/DbEntity/EnergyProvider.Extensions.cs new file mode 100644 index 0000000..c9c6250 --- /dev/null +++ b/CH.Dal/DbEntity/EnergyProvider.Extensions.cs @@ -0,0 +1,8 @@ +using CH.Dal.Abstractions; + +namespace CH.Dal.DbEntity; + +public partial class EnergyProvider : IHasId +{ + +} \ No newline at end of file diff --git a/CH.Dal/DbEntity/EnergyRate.Extension.cs b/CH.Dal/DbEntity/EnergyRate.Extension.cs index 138f883..21b1bdf 100644 --- a/CH.Dal/DbEntity/EnergyRate.Extension.cs +++ b/CH.Dal/DbEntity/EnergyRate.Extension.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations.Schema; +using CH.Dal.Abstractions; using CH.Enum; namespace CH.Dal.DbEntity; -public partial class EnergyRate +public partial class EnergyRate : IHasId { [Column(TypeName = "currency")] public Currency Currency { get; set; } -} \ No newline at end of file +} diff --git a/CH.Dal/DbEntity/EnergyRateException.Extension.cs b/CH.Dal/DbEntity/EnergyRateException.Extension.cs index 6d57040..a5ddda9 100644 --- a/CH.Dal/DbEntity/EnergyRateException.Extension.cs +++ b/CH.Dal/DbEntity/EnergyRateException.Extension.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations.Schema; +using CH.Dal.Abstractions; using CH.Enum; namespace CH.Dal.DbEntity; -public partial class EnergyRateException +public partial class EnergyRateException : IHasId { - [Column(TypeName = "energy_rate_exception_treshold_reset_type")] + [Column(TypeName = "energy_rate_exception_threshold_reset_type")] public EnergyRateExceptionThresholdResetType ResetType { get; set; } } \ No newline at end of file diff --git a/CH.Dal/DbEntity/EnergyRateUpdate.cs b/CH.Dal/DbEntity/EnergyRateUpdate.cs index f273470..5fce1ff 100644 --- a/CH.Dal/DbEntity/EnergyRateUpdate.cs +++ b/CH.Dal/DbEntity/EnergyRateUpdate.cs @@ -9,14 +9,12 @@ public partial class EnergyRateUpdate public long? RateId { get; set; } - public bool SendAlert { get; set; } + public string Name { get; set; } = null!; public DateTime StartedAt { get; set; } public decimal? Rate { get; set; } - public DateTime? AppliedAt { get; set; } - public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } diff --git a/CH.Dal/Validators/DbEntityExistValidator.cs b/CH.Dal/Validators/DbEntityExistValidator.cs new file mode 100644 index 0000000..a85b946 --- /dev/null +++ b/CH.Dal/Validators/DbEntityExistValidator.cs @@ -0,0 +1,25 @@ +using CH.Dal.Abstractions; +using FluentValidation; +using Microsoft.EntityFrameworkCore; + +namespace CH.Dal.Validators; + +public class DbEntityExistValidator : AbstractValidator + where TEntity : class, IHasId +{ + private readonly CHDbContext _dbContext; + + public DbEntityExistValidator(CHDbContext dbContext) + { + _dbContext = dbContext; + + RuleFor(value => value) + .NotNull() + .MustAsync(IsEntityExist) + .WithMessage("Invalid Entity"); + } + + private Task IsEntityExist(TValue id, + CancellationToken cancellationToken) + => _dbContext.Set().AnyAsync(entity => entity.Id!.Equals(id), cancellationToken); +} \ No newline at end of file diff --git a/CH.KeycloakApi/KeycloakService.cs b/CH.KeycloakApi/KeycloakService.cs index 86fa16b..29c6e0d 100644 --- a/CH.KeycloakApi/KeycloakService.cs +++ b/CH.KeycloakApi/KeycloakService.cs @@ -1,12 +1,12 @@ - using System.Text; - using IdentityModel.Client; - using Microsoft.Extensions.Configuration; - using Newtonsoft.Json; +using System.Text; +using IdentityModel.Client; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; - namespace CH.KeycloakApi; +namespace CH.KeycloakApi; - public class KeycloakService - { +public class KeycloakService +{ private readonly KeycloakSettings settings; public KeycloakService(IConfiguration configuration) @@ -17,9 +17,10 @@ public KeycloakSettings Settings => settings; - public async Task GetTokenAsync() + // use token manager instead + public async Task GetTokenAsync(CancellationToken cancellationToken = default) { - var tokenEndpoint = $"{this.settings.Endpoint}/realms/master/protocol/openid-connect/token"; + var tokenEndpoint = $"{this.settings.Endpoint}/realms/${settings.ApiRealm}/protocol/openid-connect/token"; var client = new HttpClient(); var response = await client.RequestTokenAsync(new TokenRequest { @@ -27,12 +28,12 @@ GrantType = "client_credentials", ClientId = this.settings.ClientId, ClientSecret = this.settings.ClientSecret, - }); + }, cancellationToken); return response.AccessToken; } - public async Task GetUserByEmailAsync(string realm, string email) + public async Task GetUserByEmailAsync(string realm, string email) { var httpClient = new HttpClient(); var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?email={email}"; @@ -40,11 +41,11 @@ var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); - var ret = JsonConvert.DeserializeObject>(json); - return ret.FirstOrDefault(); + var keycloakUsers = JsonConvert.DeserializeObject>(json); + return keycloakUsers?.FirstOrDefault(); } - public async Task GetUserByIdAsync(string realm, string id) + public async Task GetUserByIdAsync(string realm, string id) { var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); @@ -52,8 +53,8 @@ var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); - var ret = JsonConvert.DeserializeObject(json); - return ret; + var keycloakUser = JsonConvert.DeserializeObject(json); + return keycloakUser; } public async Task SendChangePasswordEmailAsync(string realm, string id) @@ -69,7 +70,7 @@ response.EnsureSuccessStatusCode(); } - public async Task> GetUsersAsync(string realm, string search = null, int max = 100) + public async Task?> GetUsersAsync(string realm, string? search = null, int max = 100) { var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); @@ -79,14 +80,15 @@ var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); - var ret = JsonConvert.DeserializeObject>(json); - return ret; + var keycloakUsers = JsonConvert.DeserializeObject>(json); + return keycloakUsers; } public async Task ChangePasswordAsync(string realm, string id, string newPassword, bool temporary) { - ///auth/admin/realms/{realm}/users/{id}/reset-password + // auth/admin/realms/{realm}/users/{id}/reset-password /////{ "type": "password", "temporary": false, "value": "my-new-password" } var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/reset-password"; var httpClient = new HttpClient(); @@ -128,7 +130,7 @@ return await this.GetUserByEmailAsync(realm, email) != null; } - public async Task CreateOrUpdateAsync(string realm, string email, string firstName, string lastName, bool enabled) + public async Task CreateOrUpdateAsync(string realm, string email, string firstName, string lastName, bool enabled) { var existingUser = await GetUserByEmailAsync(realm, email); if (existingUser != null) @@ -140,7 +142,7 @@ return await CreateUserAsync(realm, email, firstName, lastName, enabled); } - public async Task CreateUserAsync(string realm, string email, string firstName, string lastName, bool enabled) + public async Task CreateUserAsync(string realm, string email, string firstName, string lastName, bool enabled) { long epochTicks = new DateTime(1970, 1, 1).Ticks; long unixTime = ((DateTime.UtcNow.Ticks - epochTicks) / TimeSpan.TicksPerSecond); @@ -174,7 +176,7 @@ var postJson = JsonConvert.SerializeObject(user); var response = await httpClient.PostAsync(url, new StringContent(postJson, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); - var ret = await GetUserByEmailAsync(realm, email); - return ret; + var keycloakUser = await GetUserByEmailAsync(realm, email); + return keycloakUser; } - } \ No newline at end of file +} \ No newline at end of file diff --git a/CH.KeycloakApi/KeycloakSettings.cs b/CH.KeycloakApi/KeycloakSettings.cs index 4049e36..0858cb5 100644 --- a/CH.KeycloakApi/KeycloakSettings.cs +++ b/CH.KeycloakApi/KeycloakSettings.cs @@ -5,6 +5,6 @@ public class KeycloakSettings public string Endpoint { get; set; } public string ClientId { get; set; } public string ClientSecret { get; set; } - public string EmployeeRealm { get; set; } - public string UserRealm { get; set; } + public string ApiRealm { get; set; } + public string Realm { get; set; } } \ No newline at end of file diff --git a/CH.KeycloakApi/KeycloakUser.cs b/CH.KeycloakApi/KeycloakUser.cs index b53de69..d90c175 100644 --- a/CH.KeycloakApi/KeycloakUser.cs +++ b/CH.KeycloakApi/KeycloakUser.cs @@ -7,28 +7,40 @@ public class KeycloakUser { [JsonProperty("id")] public string Id { get; set; } + [JsonProperty("createdTimestamp")] public long CreatedTimestamp { get; set; } + [JsonProperty("username")] public string Username { get; set; } + [JsonProperty("enabled")] public bool Enabled { get; set; } + [JsonProperty("totp")] public bool Totp { get; set; } + [JsonProperty("emailVerified")] public bool EmailVerified { get; set; } + [JsonProperty("firstName")] public string FirstName { get; set; } + [JsonProperty("lastName")] public string LastName { get; set; } + [JsonProperty("email")] public string Email { get; set; } + [JsonProperty("notBefore")] public int NotBefore { get; set; } + [JsonProperty("requiredActions")] public List RequiredActions { get; set; } + [JsonProperty("attributes")] - public JObject Attributes { get; set; } + public JObject? Attributes { get; set; } + [JsonProperty("access")] public Dictionary Access { get; set; } } \ No newline at end of file diff --git a/ConstellationHeating.sln b/ConstellationHeating.sln index 54dd1ae..731e724 100644 --- a/ConstellationHeating.sln +++ b/ConstellationHeating.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.Enum", "CH.Enum\CH.Enum. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.KeycloakApi", "CH.KeycloakApi\CH.KeycloakApi.csproj", "{28648B65-7D84-460A-993E-0F98D94CECFB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.Dal.Abstractions", "CH.Dal.Abstractions\CH.Dal.Abstractions.csproj", "{C1584D53-DEC2-42B4-BEC9-3CCEECA88CB6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +77,10 @@ Global {28648B65-7D84-460A-993E-0F98D94CECFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {28648B65-7D84-460A-993E-0F98D94CECFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {28648B65-7D84-460A-993E-0F98D94CECFB}.Release|Any CPU.Build.0 = Release|Any CPU + {C1584D53-DEC2-42B4-BEC9-3CCEECA88CB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1584D53-DEC2-42B4-BEC9-3CCEECA88CB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1584D53-DEC2-42B4-BEC9-3CCEECA88CB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1584D53-DEC2-42B4-BEC9-3CCEECA88CB6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE