Compare commits
9 Commits
main
...
feature/en
Author | SHA1 | Date | |
---|---|---|---|
04ded7c21b | |||
1451314d56 | |||
8eefa30d52 | |||
77fdada230 | |||
aa61d945ed | |||
caf6f3275c | |||
20ed8e38f7 | |||
f0f4c6970a | |||
46911ceaa1 |
@ -2,6 +2,9 @@
|
||||
using CH.CQRS;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
using CH.Dal;
|
||||
using CH.CryptoStats.CoinMarketCap;
|
||||
using CH.CryptoStats.CoinGecko;
|
||||
|
||||
|
||||
namespace CH.Api;
|
||||
|
||||
@ -13,7 +16,8 @@ public class AppModule : IModule
|
||||
services.AddModule<SharedModule>();
|
||||
services.AddModule<CommandModule>();
|
||||
services.AddModule<QueryModule>();
|
||||
|
||||
services.AddModule<CoinMarketCapModule>();
|
||||
services.AddModule<CoinGeckoModule>();
|
||||
services.AddModule<AuthorityModule>();
|
||||
|
||||
return services;
|
||||
|
@ -34,6 +34,8 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.Authority\CH.Authority.csproj" />
|
||||
<ProjectReference Include="..\CH.CQRS\CH.CQRS.csproj" />
|
||||
<ProjectReference Include="..\CH.CryptoStats.CoinGecko\CH.CryptoStats.CoinGecko.csproj" />
|
||||
<ProjectReference Include="..\CH.CryptoStats.CoinMarketCap\CH.CryptoStats.CoinMarketCap.csproj" />
|
||||
<ProjectReference Include="..\CH.Dal\CH.Dal.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using CH.Api;
|
||||
using CH.CryptoStats.Abstractions;
|
||||
using CH.Dal;
|
||||
using FluentValidation.AspNetCore;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
@ -16,6 +17,7 @@ using PoweredSoft.Data.EntityFrameworkCore;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
using System.Text.Json.Serialization;
|
||||
using CH.Enum;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -39,10 +41,9 @@ builder.Services.AddDefaultCommandDiscovery();
|
||||
builder.Services.AddDefaultQueryDiscovery();
|
||||
builder.Services.AddFluentValidation();
|
||||
builder.Services.AddModule<AppModule>();
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddDefaultCommandDiscovery();
|
||||
builder.Services.AddDefaultQueryDiscovery();
|
||||
|
||||
if (builder.Configuration.GetValue<bool>("Swagger:Enable"))
|
||||
{
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
@ -110,7 +111,8 @@ mvcBuilder
|
||||
|
||||
var connectionString = builder.Configuration.GetSection("Database").GetValue<string>("ConnectionString");
|
||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
||||
|
||||
dataSourceBuilder.MapEnum<Currency>("currency");
|
||||
dataSourceBuilder.MapEnum<EnergyRateExceptionThresholdResetType>("energy_rate_exception_threshold_reset_type");
|
||||
var dataSource = dataSourceBuilder.Build();
|
||||
|
||||
builder.Services.AddDbContextPool<CHDbContext>(options =>
|
||||
|
@ -18,5 +18,19 @@
|
||||
"TokenUrl": "",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"CoinMarketCap": {
|
||||
"ApiUrl": "",
|
||||
"ApiKey": ""
|
||||
|
||||
},
|
||||
"CoinGecko": {
|
||||
"ApiUrl": "",
|
||||
"ApiKey": ""
|
||||
|
||||
},
|
||||
"HydroQuebec": {
|
||||
"ApiUrl": ""
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.Authority\CH.Authority.csproj" />
|
||||
<ProjectReference Include="..\CH.CryptoStats.CoinGecko\CH.CryptoStats.CoinGecko.csproj" />
|
||||
<ProjectReference Include="..\CH.CryptoStats.CoinMarketCap\CH.CryptoStats.CoinMarketCap.csproj" />
|
||||
<ProjectReference Include="..\CH.CryptoStats\CH.CryptoStats.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\CH.Dal\CH.Dal.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
30
CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs
Normal file
30
CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using CH.CQRS.Service.CryptoStats;
|
||||
using CH.CQRS.Service.User;
|
||||
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.CryptoStats;
|
||||
|
||||
public class GetCryptoStatsCommand
|
||||
{
|
||||
}
|
||||
|
||||
public class GetCryptoStatsCommandHandler(CryptoService cryptoService) : ICommandHandler<GetCryptoStatsCommand>
|
||||
{
|
||||
public Task HandleAsync(GetCryptoStatsCommand command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return cryptoService.GetCryptoStatsAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class GetCryptoStatsCommandValidator : AbstractValidator<GetCryptoStatsCommand>
|
||||
{
|
||||
public GetCryptoStatsCommandValidator()
|
||||
{
|
||||
}
|
||||
}
|
13
CH.CQRS/Command/CryptoStats/ServiceCollectionExtension.cs
Normal file
13
CH.CQRS/Command/CryptoStats/ServiceCollectionExtension.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.FluentValidation;
|
||||
|
||||
namespace CH.CQRS.Command.CryptoStats;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddCryptoCommand(this IServiceCollection services)
|
||||
{
|
||||
services.AddCommand<GetCryptoStatsCommand, GetCryptoStatsCommandHandler, GetCryptoStatsCommandValidator>();
|
||||
return services;
|
||||
}
|
||||
}
|
43
CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs
Normal file
43
CH.CQRS/Command/Energy/CreateEnergyProviderCommand.cs
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
public class CreateEnergyProviderCommand
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
public class CreateEnergyProviderCommandHandler(EnergyService energyService) : ICommandHandler<CreateEnergyProviderCommand>
|
||||
{
|
||||
public Task HandleAsync(CreateEnergyProviderCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.CreateEnergyProviderAsync(new CreateEnergyProviderCommandOptions
|
||||
{
|
||||
Name = command.Name,
|
||||
Active = command.Active
|
||||
},cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateEnergyProviderCommandValidator : AbstractValidator<CreateEnergyProviderCommand>
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
55
CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs
Normal file
55
CH.CQRS/Command/Energy/CreateEnergyRateCommand.cs
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
||||
|
||||
public class CreateEnergyRateCommand
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public Currency Currency { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
public class CreateEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler<CreateEnergyRateCommand>
|
||||
{
|
||||
public Task HandleAsync(CreateEnergyRateCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.CreateEnergyRateAsync(new CreateEnergyRateCommandOptions
|
||||
{
|
||||
ProviderId = command.ProviderId,
|
||||
Name = command.Name,
|
||||
Price = command.Price,
|
||||
Currency = command.Currency,
|
||||
Active = command.Active
|
||||
},cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateEnergyRateCommandValidator : AbstractValidator<CreateEnergyRateCommand>
|
||||
{
|
||||
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()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyProvider,long>(dbContext))
|
||||
.WithMessage("The provided provider Id is invalid.");
|
||||
}
|
||||
}
|
61
CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs
Normal file
61
CH.CQRS/Command/Energy/CreateEnergyRateExceptionCommand.cs
Normal file
@ -0,0 +1,61 @@
|
||||
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;
|
||||
|
||||
public class CreateEnergyRateExceptionCommand
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public decimal EnergyThreshold { get; set; }
|
||||
public EnergyRateExceptionThresholdResetType ResetType { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? EndedAt { get; set; }
|
||||
}
|
||||
|
||||
public class CreateEnergyRateExceptionCommandHandler(EnergyService energyService) : ICommandHandler<CreateEnergyRateExceptionCommand>
|
||||
{
|
||||
public Task HandleAsync(CreateEnergyRateExceptionCommand command,
|
||||
CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.CreateEnergyRateExceptionAsync(new CreateEnergyRateExceptionCommandOptions
|
||||
{
|
||||
RateId = command.RateId,
|
||||
Name = command.Name,
|
||||
EnergyThreshold = command.EnergyThreshold,
|
||||
ResetType = command.ResetType,
|
||||
StartedAt = command.StartedAt,
|
||||
EndedAt = command.EndedAt
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
public class CreateEnergyRateExceptionCommandValidator : AbstractValidator<CreateEnergyRateExceptionCommand>
|
||||
{
|
||||
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()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyRate,long>(dbContext))
|
||||
.WithMessage("The provided Rate Id is invalid.");
|
||||
}
|
||||
}
|
38
CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs
Normal file
38
CH.CQRS/Command/Energy/DisableEnergyProviderCommand.cs
Normal file
@ -0,0 +1,38 @@
|
||||
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;
|
||||
|
||||
namespace CH.CQRS.Command.Energy;
|
||||
|
||||
public class DisableEnergyProviderCommand
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
}
|
||||
public class DisableEnergyProviderCommandHandler(EnergyService energyService) : ICommandHandler<DisableEnergyProviderCommand>
|
||||
{
|
||||
public Task HandleAsync(DisableEnergyProviderCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.DisableEnergyProviderAsync(new DisableEnergyProviderCommandOptions
|
||||
{
|
||||
ProviderId = command.ProviderId,
|
||||
DisabledAt = command.DisabledAt,
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class DisableEnergyProviderCommandValidator : AbstractValidator<DisableEnergyProviderCommand>
|
||||
{
|
||||
public DisableEnergyProviderCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.ProviderId)
|
||||
.NotEmpty()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyProvider,long>(dbContext))
|
||||
.WithMessage("The provided provider Id is invalid.");
|
||||
|
||||
}
|
||||
}
|
37
CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs
Normal file
37
CH.CQRS/Command/Energy/DisableEnergyRateCommand.cs
Normal file
@ -0,0 +1,37 @@
|
||||
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;
|
||||
|
||||
namespace CH.CQRS.Command.Energy;
|
||||
|
||||
public class DisableEnergyRateCommand
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
}
|
||||
public class DisableEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler<DisableEnergyRateCommand>
|
||||
{
|
||||
public Task HandleAsync(DisableEnergyRateCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.DisableEnergyRateAsync(new DisableEnergyRateCommandOptions
|
||||
{
|
||||
RateId = command.RateId,
|
||||
DisabledAt = command.DisabledAt
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class DisableEnergyRateCommandValidator : AbstractValidator<DisableEnergyRateCommand>
|
||||
{
|
||||
public DisableEnergyRateCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.RateId)
|
||||
.NotEmpty()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyRate,long>(dbContext))
|
||||
.WithMessage("The provided Rate Id is invalid.");
|
||||
}
|
||||
}
|
36
CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs
Normal file
36
CH.CQRS/Command/Energy/EnableEnergyProviderCommand.cs
Normal file
@ -0,0 +1,36 @@
|
||||
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;
|
||||
|
||||
namespace CH.CQRS.Command.Energy;
|
||||
|
||||
public class EnableEnergyProviderCommand
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
}
|
||||
public class EnableEnergyProviderCommandHandler(EnergyService energyService) : ICommandHandler<EnableEnergyProviderCommand>
|
||||
{
|
||||
public Task HandleAsync(EnableEnergyProviderCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.EnableEnergyProviderAsync(new EnableEnergyProviderCommandOptions()
|
||||
{
|
||||
ProviderId = command.ProviderId
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class EnableEnergyProviderCommandValidator : AbstractValidator<EnableEnergyProviderCommand>
|
||||
{
|
||||
public EnableEnergyProviderCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.ProviderId)
|
||||
.NotEmpty()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyProvider,long>(dbContext))
|
||||
.WithMessage("The provided provider Id is invalid.");
|
||||
|
||||
}
|
||||
}
|
34
CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs
Normal file
34
CH.CQRS/Command/Energy/EnableEnergyRateCommand.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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;
|
||||
|
||||
public class EnableEnergyRateCommand
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
}
|
||||
public class EnableEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler<EnableEnergyRateCommand>
|
||||
{
|
||||
public Task HandleAsync(EnableEnergyRateCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.EnableEnergyRateAsync(new EnableEnergyRateCommandOptions
|
||||
{
|
||||
RateId = command.RateId
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class EnableEnergyRateCommandValidator : AbstractValidator<EnableEnergyRateCommand>
|
||||
{
|
||||
public EnableEnergyRateCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.RateId)
|
||||
.NotEmpty()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyRate,long>(dbContext))
|
||||
.WithMessage("The provided Rate Id is invalid.");
|
||||
}
|
||||
}
|
43
CH.CQRS/Command/Energy/ServiceCollectionExtension.cs
Normal file
43
CH.CQRS/Command/Energy/ServiceCollectionExtension.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using OpenHarbor.CQRS.FluentValidation;
|
||||
namespace CH.CQRS.Command.Energy;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddEnergyCommand(this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddCommand<CreateEnergyProviderCommand, CreateEnergyProviderCommandHandler,
|
||||
CreateEnergyProviderCommandValidator>();
|
||||
services
|
||||
.AddCommand<CreateEnergyRateCommand, CreateEnergyRateCommandHandler,
|
||||
CreateEnergyRateCommandValidator>();
|
||||
services
|
||||
.AddCommand<CreateEnergyRateExceptionCommand, CreateEnergyRateExceptionCommandHandler,
|
||||
CreateEnergyRateExceptionCommandValidator>();
|
||||
services
|
||||
.AddCommand<DisableEnergyProviderCommand, DisableEnergyProviderCommandHandler,
|
||||
DisableEnergyProviderCommandValidator>();
|
||||
services
|
||||
.AddCommand<DisableEnergyRateCommand, DisableEnergyRateCommandHandler,
|
||||
DisableEnergyRateCommandValidator>();
|
||||
services
|
||||
.AddCommand<EnableEnergyProviderCommand, EnableEnergyProviderCommandHandler,
|
||||
EnableEnergyProviderCommandValidator>();
|
||||
services
|
||||
.AddCommand<EnableEnergyRateCommand, EnableEnergyRateCommandHandler,
|
||||
EnableEnergyRateCommandValidator>();
|
||||
services
|
||||
.AddCommand<UpdateEnergyProviderCommand, UpdateEnergyProviderCommandHandler,
|
||||
UpdateEnergyProviderCommandValidator>();
|
||||
services
|
||||
.AddCommand<UpdateEnergyRateCommand, UpdateEnergyRateCommandHandler,
|
||||
UpdateEnergyRateCommandValidator>();
|
||||
services
|
||||
.AddCommand<UpdateEnergyRateExceptionCommand, UpdateEnergyRateExceptionCommandHandler,
|
||||
UpdateEnergyRateExceptionCommandValidator>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
47
CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs
Normal file
47
CH.CQRS/Command/Energy/UpdateEnergyProviderCommand.cs
Normal file
@ -0,0 +1,47 @@
|
||||
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;
|
||||
|
||||
public class UpdateEnergyProviderCommand
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateEnergyProviderCommandHandler(EnergyService energyService) : ICommandHandler<UpdateEnergyProviderCommand>
|
||||
{
|
||||
public Task HandleAsync(UpdateEnergyProviderCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.UpdateEnergyProviderAsync(new UpdateEnergyProviderCommandOptions
|
||||
{
|
||||
ProviderId = command.ProviderId,
|
||||
Name = command.Name
|
||||
},cancellationToken);
|
||||
}
|
||||
}
|
||||
public class UpdateEnergyProviderCommandValidator : AbstractValidator<UpdateEnergyProviderCommand>
|
||||
{
|
||||
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()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyProvider,long>(dbContext));
|
||||
}
|
||||
}
|
57
CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs
Normal file
57
CH.CQRS/Command/Energy/UpdateEnergyRateCommand.cs
Normal file
@ -0,0 +1,57 @@
|
||||
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;
|
||||
|
||||
public class UpdateEnergyRateCommand
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public decimal? Rate { get; set; }
|
||||
}
|
||||
public class UpdateEnergyRateCommandHandler(EnergyService energyService) : ICommandHandler<UpdateEnergyRateCommand>
|
||||
{
|
||||
public Task HandleAsync(UpdateEnergyRateCommand command, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.UpdateEnergyRateAsync(new UpdateEnergyRateCommandOptions
|
||||
{
|
||||
RateId = command.RateId,
|
||||
Name = command.Name,
|
||||
StartedAt = command.StartedAt,
|
||||
Rate = command.Rate
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateEnergyRateCommandValidator : AbstractValidator<UpdateEnergyRateCommand>
|
||||
{
|
||||
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<EnergyRate,long>(dbContext))
|
||||
.WithMessage("The provided Rate Id is invalid.");
|
||||
|
||||
RuleFor(command => command.Rate)
|
||||
.GreaterThanOrEqualTo(0);
|
||||
}
|
||||
}
|
49
CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs
Normal file
49
CH.CQRS/Command/Energy/UpdateEnergyRateExceptionCommand.cs
Normal file
@ -0,0 +1,49 @@
|
||||
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;
|
||||
|
||||
public class UpdateEnergyRateExceptionCommand
|
||||
{
|
||||
public long EnergyRateExceptionId { get; set; }
|
||||
public decimal? EnergyThreshold { get; set; }
|
||||
public EnergyRateExceptionThresholdResetType ResetType { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? EndedAt { get; set; }
|
||||
}
|
||||
public class UpdateEnergyRateExceptionCommandHandler(EnergyService energyService) : ICommandHandler<UpdateEnergyRateExceptionCommand>
|
||||
{
|
||||
public Task HandleAsync(UpdateEnergyRateExceptionCommand command,
|
||||
CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return energyService.UpdateEnergyRateExceptionAsync(new UpdateEnergyRateExceptionCommandOptions
|
||||
{
|
||||
EnergyRateExceptionId = command.EnergyRateExceptionId,
|
||||
EnergyThreshold = command.EnergyThreshold,
|
||||
ResetType = command.ResetType,
|
||||
StartedAt = command.StartedAt,
|
||||
EndedAt = command.EndedAt
|
||||
},cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateEnergyRateExceptionCommandValidator : AbstractValidator<UpdateEnergyRateExceptionCommand>
|
||||
{
|
||||
public UpdateEnergyRateExceptionCommandValidator(CHDbContext dbContext)
|
||||
{
|
||||
RuleFor(command => command.EnergyRateExceptionId)
|
||||
.NotEmpty()
|
||||
.SetValidator(new DbEntityExistValidator<EnergyRateException,long>(dbContext));
|
||||
|
||||
RuleFor(command => command.EnergyThreshold)
|
||||
.GreaterThan(0)
|
||||
.When(command => command.EnergyThreshold.HasValue);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using CH.CQRS.Command.User;
|
||||
using CH.CQRS.Command.CryptoStats;
|
||||
using CH.CQRS.Command.User;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
using System;
|
||||
@ -6,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CH.CQRS.Command.Energy;
|
||||
|
||||
namespace CH.CQRS;
|
||||
public class CommandModule : IModule
|
||||
@ -13,7 +15,8 @@ public class CommandModule : IModule
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddUserCommand();
|
||||
|
||||
services.AddCryptoCommand();
|
||||
services.AddEnergyCommand();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
22
CH.CQRS/Query/EnergyProvider/EnergyProviderQuery.cs
Normal file
22
CH.CQRS/Query/EnergyProvider/EnergyProviderQuery.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using CH.Dal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace CH.CQRS.Query.EnergyProvider;
|
||||
|
||||
public class EnergyProviderQuery
|
||||
{
|
||||
|
||||
}
|
||||
public class EnergyProviderQueryHandler(CHDbContext dbContext) : IQueryHandler<EnergyProviderQuery, EnergyProviderQueryResult>
|
||||
{
|
||||
public async Task<EnergyProviderQueryResult> HandleAsync(EnergyProviderQuery query, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
var energyProviders = await dbContext.EnergyProviders.ToListAsync(cancellationToken);
|
||||
var energyProviderQueryResult = new EnergyProviderQueryResult
|
||||
{
|
||||
Data = energyProviders
|
||||
};
|
||||
return energyProviderQueryResult;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CH.CQRS.Query.EnergyProvider;
|
||||
|
||||
public class EnergyProviderQueryResult
|
||||
{
|
||||
public required List<Dal.DbEntity.EnergyProvider> Data { get; set; }
|
||||
}
|
23
CH.CQRS/Query/EnergyRate/EnergyRateQuery.cs
Normal file
23
CH.CQRS/Query/EnergyRate/EnergyRateQuery.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using CH.CQRS.Query.EnergyProvider;
|
||||
using CH.Dal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace CH.CQRS.Query.EnergyRate;
|
||||
|
||||
public class EnergyRateQuery
|
||||
{
|
||||
|
||||
}
|
||||
public class EnergyRateQueryHandler(CHDbContext dbContext) : IQueryHandler<EnergyRateQuery, EnergyRateQueryResult>
|
||||
{
|
||||
public async Task<EnergyRateQueryResult> HandleAsync(EnergyRateQuery query, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
var energyRates = await dbContext.EnergyRates.ToListAsync(cancellationToken);
|
||||
var energyRateQueryResult = new EnergyRateQueryResult
|
||||
{
|
||||
Data = energyRates
|
||||
};
|
||||
return energyRateQueryResult;
|
||||
}
|
||||
}
|
6
CH.CQRS/Query/EnergyRate/EnergyRateQueryResult.cs
Normal file
6
CH.CQRS/Query/EnergyRate/EnergyRateQueryResult.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace CH.CQRS.Query.EnergyRate;
|
||||
|
||||
public class EnergyRateQueryResult
|
||||
{
|
||||
public required List<Dal.DbEntity.EnergyRate> Data { get; set; }
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using CH.Dal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace CH.CQRS.Query.EnergyRateException;
|
||||
|
||||
public class EnergyRateExceptionQuery
|
||||
{
|
||||
|
||||
}
|
||||
public class EnergyRateExceptionQueryHandler(CHDbContext dbContext) : IQueryHandler<EnergyRateExceptionQuery, EnergyRateExceptionQueryResult>
|
||||
{
|
||||
public async Task<EnergyRateExceptionQueryResult> HandleAsync(EnergyRateExceptionQuery query, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
var energyRateExceptions = await dbContext.EnergyRateExceptions.ToListAsync(cancellationToken);
|
||||
var energyRateExceptionsQueryResult = new EnergyRateExceptionQueryResult
|
||||
{
|
||||
Data = energyRateExceptions,
|
||||
};
|
||||
return energyRateExceptionsQueryResult;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CH.CQRS.Query.EnergyRateException;
|
||||
|
||||
public class EnergyRateExceptionQueryResult
|
||||
{
|
||||
public required List<Dal.DbEntity.EnergyRateException> Data { get; set; }
|
||||
}
|
@ -7,6 +7,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CH.CQRS.Query.EnergyProvider;
|
||||
using CH.CQRS.Query.EnergyRate;
|
||||
using CH.CQRS.Query.EnergyRateException;
|
||||
|
||||
namespace CH.CQRS;
|
||||
public class QueryModule : IModule
|
||||
@ -14,6 +17,9 @@ public class QueryModule : IModule
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddQuery<HealthQuery, HealthQueryResult, HealthQueryHandler>();
|
||||
services.AddQuery<EnergyProviderQuery, EnergyProviderQueryResult, EnergyProviderQueryHandler>();
|
||||
services.AddQuery<EnergyRateQuery, EnergyRateQueryResult, EnergyRateQueryHandler>();
|
||||
services.AddQuery<EnergyRateExceptionQuery, EnergyRateExceptionQueryResult, EnergyRateExceptionQueryHandler>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
12
CH.CQRS/Service/CryptoStats/CryptoService.cs
Normal file
12
CH.CQRS/Service/CryptoStats/CryptoService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using CH.CryptoStats.CoinGecko;
|
||||
using CH.CryptoStats.CoinMarketCap;
|
||||
|
||||
namespace CH.CQRS.Service.CryptoStats;
|
||||
|
||||
public class CryptoService(CoinGeckoService coinGecko)
|
||||
{
|
||||
public async Task GetCryptoStatsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var crypto = await coinGecko.GetCryptoStatsAsync("bitcoin", "cad", cancellationToken);
|
||||
}
|
||||
}
|
187
CH.CQRS/Service/Energy/EnergyService.cs
Normal file
187
CH.CQRS/Service/Energy/EnergyService.cs
Normal file
@ -0,0 +1,187 @@
|
||||
using CH.CQRS.Command.Energy;
|
||||
using CH.CQRS.Service.Energy.Options;
|
||||
using CH.Dal;
|
||||
using CH.Dal.DbEntity;
|
||||
using CH.Enum;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CH.CQRS.Service.Energy;
|
||||
|
||||
public class EnergyService(CHDbContext dbContext)
|
||||
{
|
||||
public async Task CreateEnergyProviderAsync(CreateEnergyProviderCommandOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var energyProvider = new EnergyProvider
|
||||
{
|
||||
Name = options.Name,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Active = options.Active
|
||||
};
|
||||
await dbContext.AddAsync(energyProvider, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
public async Task CreateEnergyRateAsync(CreateEnergyRateCommandOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var provider = await dbContext.EnergyProviders.FirstOrDefaultAsync(provider => provider.Id == options.ProviderId, cancellationToken);
|
||||
var energyRate = new EnergyRate
|
||||
{
|
||||
Name = options.Name,
|
||||
Currency = options.Currency,
|
||||
Price = options.Price,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Provider = provider,
|
||||
Active = options.Active
|
||||
};
|
||||
await dbContext.AddAsync(energyRate, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
public async Task CreateEnergyRateExceptionAsync(CreateEnergyRateExceptionCommandOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var energyRate = await dbContext.EnergyRates.FirstOrDefaultAsync(energyRate => energyRate.Id == options.RateId,cancellationToken);
|
||||
var energyRateException = new EnergyRateException
|
||||
{
|
||||
Name = options.Name,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Rate = energyRate,
|
||||
StartedAt = options.StartedAt,
|
||||
EndedAt = options.EndedAt,
|
||||
EnergyThreshold = options.EnergyThreshold,
|
||||
ResetType = options.ResetType,
|
||||
};
|
||||
await dbContext.AddAsync(energyRateException, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UpdateEnergyProviderAsync(UpdateEnergyProviderCommandOptions options,
|
||||
CancellationToken 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)
|
||||
{
|
||||
if (options.StartedAt.HasValue)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task UpdateEnergyRateExceptionAsync(UpdateEnergyRateExceptionCommandOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var energyRateException = await dbContext.EnergyRateExceptions
|
||||
.AsTracking()
|
||||
.FirstOrDefaultAsync(rateException => rateException.Id == options.EnergyRateExceptionId, cancellationToken);
|
||||
|
||||
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 (energyRateException.ResetType != options.ResetType)
|
||||
{
|
||||
energyRateException.ResetType = options.ResetType;
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DisableEnergyProviderAsync(DisableEnergyProviderCommandOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var energyProvider = await dbContext.EnergyProviders.FirstOrDefaultAsync(provider => provider.Id == options.ProviderId, cancellationToken);
|
||||
if (null != energyProvider)
|
||||
{
|
||||
energyProvider.Active = false;
|
||||
energyProvider.DisabledAt = options.DisabledAt ?? DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisableEnergyRateAsync(DisableEnergyRateCommandOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var energyRate = await dbContext.EnergyRates.FirstOrDefaultAsync(rate => rate.Id == options.RateId, cancellationToken);
|
||||
if (null != energyRate)
|
||||
{
|
||||
energyRate.Active = false;
|
||||
energyRate.DisabledAt = options.DisabledAt ?? DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
public async Task EnableEnergyProviderAsync(EnableEnergyProviderCommandOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var energyProvider = await dbContext.EnergyProviders.FirstOrDefaultAsync(provider => provider.Id == options.ProviderId, cancellationToken);
|
||||
if (null != energyProvider)
|
||||
{
|
||||
energyProvider.Active = true;
|
||||
energyProvider.DisabledAt = null;
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnableEnergyRateAsync(EnableEnergyRateCommandOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var energyRate = await dbContext.EnergyRates.FirstOrDefaultAsync(rate => rate.Id == options.RateId, cancellationToken);
|
||||
if (null != energyRate)
|
||||
{
|
||||
energyRate.Active = true;
|
||||
energyRate.DisabledAt = null;
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class CreateEnergyProviderCommandOptions
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using Amazon.Runtime.Internal.Transform;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class CreateEnergyRateCommandOptions
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public decimal? Price { get; set; }
|
||||
public Currency Currency { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class CreateEnergyRateExceptionCommandOptions
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public decimal EnergyThreshold { get; set; }
|
||||
public EnergyRateExceptionThresholdResetType ResetType { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? EndedAt { get; set; }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class DisableEnergyProviderCommandOptions
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class DisableEnergyRateCommandOptions
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class EnableEnergyProviderCommandOptions
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CH.CQRS.Command.Energy;
|
||||
|
||||
public class EnableEnergyRateCommandOptions
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class UpdateEnergyProviderCommandOptions
|
||||
{
|
||||
public long ProviderId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace CH.CQRS.Service.Energy.Options;
|
||||
|
||||
public class UpdateEnergyRateCommandOptions
|
||||
{
|
||||
public long RateId { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public decimal? Rate { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
@ -0,0 +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 EnergyRateExceptionThresholdResetType ResetType { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? EndedAt { get; set; }
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
using CH.CQRS.Service.User;
|
||||
using CH.CQRS.Service.CryptoStats;
|
||||
using CH.CQRS.Service.Energy;
|
||||
using CH.CQRS.Service.User;
|
||||
using CH.CryptoStats.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
|
||||
@ -8,6 +11,8 @@ public class SharedModule : IModule
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<UserService>();
|
||||
services.AddScoped<CryptoService>();
|
||||
services.AddScoped<EnergyService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
18
CH.CryptoStats.CoinGecko/CH.CryptoStats.CoinGecko.csproj
Normal file
18
CH.CryptoStats.CoinGecko/CH.CryptoStats.CoinGecko.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="10.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageReference Include="OpenHarbor.CQRS.DynamicQuery.Abstractions" Version="8.0.0-preview.6" />
|
||||
<PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" />
|
||||
<PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.CryptoStats\CH.CryptoStats.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
24
CH.CryptoStats.CoinGecko/CoinGeckoEntity.cs
Normal file
24
CH.CryptoStats.CoinGecko/CoinGeckoEntity.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
namespace CH.CryptoStats.CoinGecko;
|
||||
public class CoinGeckoEntity
|
||||
{
|
||||
[JsonPropertyName("symbol")]
|
||||
public required string Symbol { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; set; }
|
||||
[JsonPropertyName("current_price")]
|
||||
public decimal Price { get; set; }
|
||||
[JsonPropertyName("market_cap")]
|
||||
public decimal MarketCap { get; set; }
|
||||
[JsonPropertyName("fully_diluted_valuation")]
|
||||
public decimal FullyDilutedValuation { get; set; }
|
||||
[JsonPropertyName("circulating_supply")]
|
||||
public decimal CirculatingSupply { get; set; }
|
||||
[JsonPropertyName("total_supply")]
|
||||
public decimal TotalSupply { get; set; }
|
||||
[JsonPropertyName("max_supply")]
|
||||
public decimal MaxSupply { get; set; }
|
||||
|
||||
[JsonPropertyName("last_updated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
15
CH.CryptoStats.CoinGecko/CoinGeckoModule.cs
Normal file
15
CH.CryptoStats.CoinGecko/CoinGeckoModule.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.Data.Core;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
|
||||
namespace CH.CryptoStats.CoinGecko;
|
||||
|
||||
public class CoinGeckoModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<CoinGeckoService>();
|
||||
return services;
|
||||
}
|
||||
}
|
38
CH.CryptoStats.CoinGecko/CoinGeckoService.cs
Normal file
38
CH.CryptoStats.CoinGecko/CoinGeckoService.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using CH.CryptoStats.Abstractions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.CryptoStats.CoinGecko;
|
||||
|
||||
public class CoinGeckoService(IConfiguration configuration, HttpClient httpClient) : ICryptoStats
|
||||
{
|
||||
public async Task<Abstractions.CryptoStats> GetCryptoStatsAsync(string cryptoName, string currency, CancellationToken cancellationToken)
|
||||
{
|
||||
var apiKey = configuration.GetValue<string>("CoinGecko:ApiKey");
|
||||
var apiUrl = configuration.GetValue<string>("CoinGecko:ApiUrl");
|
||||
var response = await httpClient.GetAsync($"{apiUrl}?ids={cryptoName}&vs_currency={currency}&x_cg_demo_api_key={apiKey}",cancellationToken);
|
||||
var jsonResponse = await response.Content.ReadFromJsonAsync<List<CoinGeckoEntity>>(cancellationToken);
|
||||
return ParseCryptoStats(jsonResponse.FirstOrDefault(), currency, cancellationToken).Result;
|
||||
}
|
||||
|
||||
private Task<Abstractions.CryptoStats> ParseCryptoStats(CoinGeckoEntity coinGeckoEntity, string currency, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var cryptoStats = new Abstractions.CryptoStats
|
||||
{
|
||||
Name = coinGeckoEntity.Name,
|
||||
Symbol = coinGeckoEntity.Symbol,
|
||||
MaxSupply = coinGeckoEntity.MaxSupply,
|
||||
CirculatingSupply = coinGeckoEntity.CirculatingSupply,
|
||||
TotalSupply = coinGeckoEntity.TotalSupply,
|
||||
Currency = (Currency)System.Enum.Parse(typeof(Currency), currency.ToUpper()),
|
||||
UpdatedAt = coinGeckoEntity.LastUpdated,
|
||||
Price = coinGeckoEntity.Price,
|
||||
MarketCap = coinGeckoEntity.MarketCap,
|
||||
FullyDilutedMarketCap = coinGeckoEntity.FullyDilutedValuation
|
||||
};
|
||||
return Task.FromResult(cryptoStats);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="10.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageReference Include="OpenHarbor.CQRS.DynamicQuery.Abstractions" Version="8.0.0-preview.6" />
|
||||
<PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" />
|
||||
<PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.CryptoStats\CH.CryptoStats.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\CH.Enum\CH.Enum.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
56
CH.CryptoStats.CoinMarketCap/CoinMarketCapEntity.cs
Normal file
56
CH.CryptoStats.CoinMarketCap/CoinMarketCapEntity.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CryptoStats.CoinMarketCap;
|
||||
public class CoinMarketCapEntity
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; set; }
|
||||
[JsonPropertyName("symbol")]
|
||||
public required string Symbol { get; set; }
|
||||
[JsonPropertyName("max_supply")]
|
||||
public int MaxSupply { get; set; }
|
||||
[JsonPropertyName("circulating_supply")]
|
||||
public int CirculatingSupply { get; set; }
|
||||
[JsonPropertyName("total_supply")]
|
||||
public int TotalSupply { get; set; }
|
||||
[JsonPropertyName("last_updated")]
|
||||
public required string LastUpdated { get; set; }
|
||||
[JsonPropertyName("quote")]
|
||||
public required Quote Quote { get; set; }
|
||||
}
|
||||
public class ApiResponse
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public required Dictionary<string, CoinMarketCapEntity> Data { get; set; }
|
||||
}
|
||||
|
||||
public class Quote
|
||||
{
|
||||
[JsonPropertyName("CAD")]
|
||||
public CurrencyValue? CAD { get; set; }
|
||||
[JsonPropertyName("USD")]
|
||||
public CurrencyValue? USD { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyValue
|
||||
{
|
||||
[JsonPropertyName("price")]
|
||||
public decimal Price { get; set; }
|
||||
[JsonPropertyName("volume_24h")]
|
||||
public decimal Volume24H { get; set; }
|
||||
[JsonPropertyName("volume_change_24h")]
|
||||
public decimal VolumeChange24H { get; set; }
|
||||
[JsonPropertyName("market_cap")]
|
||||
public decimal MarketCap { get; set; }
|
||||
[JsonPropertyName("market_cap_dominance")]
|
||||
public decimal MarketCapDominance { get; set; }
|
||||
[JsonPropertyName("fully_diluted_market_cap")]
|
||||
public decimal FullyDilutedMarketCap { get; set; }
|
||||
}
|
15
CH.CryptoStats.CoinMarketCap/CoinMarketCapModule.cs
Normal file
15
CH.CryptoStats.CoinMarketCap/CoinMarketCapModule.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.Data.Core;
|
||||
using PoweredSoft.Module.Abstractions;
|
||||
|
||||
namespace CH.CryptoStats.CoinMarketCap;
|
||||
|
||||
public class CoinMarketCapModule : IModule
|
||||
{
|
||||
public IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<CoinMarketCapService>();
|
||||
return services;
|
||||
}
|
||||
}
|
42
CH.CryptoStats.CoinMarketCap/CoinMarketCapService.cs
Normal file
42
CH.CryptoStats.CoinMarketCap/CoinMarketCapService.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using CH.CryptoStats.Abstractions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.CryptoStats.CoinMarketCap;
|
||||
|
||||
public class CoinMarketCapService(HttpClient httpClient, IConfiguration configuration) : ICryptoStats
|
||||
{
|
||||
public async Task<Abstractions.CryptoStats> GetCryptoStatsAsync(string cryptoName, string currency, CancellationToken cancellationToken)
|
||||
{
|
||||
var apiKey = configuration.GetValue<string>("CoinMarketCap:ApiKey");
|
||||
var apiUrl = configuration.GetValue<string>("CoinMarketCap:ApiUrl");
|
||||
var response = await httpClient.GetAsync($"{apiUrl}slug={cryptoName}&convert={currency}&CMC_PRO_API_KEY={apiKey}",cancellationToken);
|
||||
var jsonResponse = await response.Content.ReadFromJsonAsync<ApiResponse>(cancellationToken);
|
||||
return ParseCryptoStats(jsonResponse.Data["1"], currency, cancellationToken).Result;
|
||||
|
||||
}
|
||||
|
||||
private Task<Abstractions.CryptoStats> ParseCryptoStats(CoinMarketCapEntity coinMarketCapEntity, string currency, CancellationToken cancellationToken)
|
||||
{
|
||||
var quoteData = coinMarketCapEntity.Quote.CAD ?? coinMarketCapEntity.Quote.USD;
|
||||
var cryptoStats = new Abstractions.CryptoStats
|
||||
{
|
||||
Name = coinMarketCapEntity.Name,
|
||||
Symbol = coinMarketCapEntity.Symbol,
|
||||
MaxSupply = coinMarketCapEntity.MaxSupply,
|
||||
CirculatingSupply = coinMarketCapEntity.CirculatingSupply,
|
||||
TotalSupply = coinMarketCapEntity.TotalSupply,
|
||||
Currency = (Currency)System.Enum.Parse(typeof(Currency), currency.ToUpper()),
|
||||
UpdatedAt = DateTime.Parse(coinMarketCapEntity.LastUpdated),
|
||||
Price = quoteData.Price,
|
||||
Volume24H = quoteData.Volume24H,
|
||||
VolumeChange24H = quoteData.VolumeChange24H,
|
||||
MarketCap = quoteData.MarketCap,
|
||||
MarketCapDominance = quoteData.MarketCapDominance,
|
||||
FullyDilutedMarketCap = quoteData.FullyDilutedMarketCap
|
||||
};
|
||||
return Task.FromResult(cryptoStats);
|
||||
}
|
||||
}
|
18
CH.CryptoStats/CH.CryptoStats.Abstractions.csproj
Normal file
18
CH.CryptoStats/CH.CryptoStats.Abstractions.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="10.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageReference Include="OpenHarbor.CQRS.DynamicQuery.Abstractions" Version="8.0.0-preview.6" />
|
||||
<PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" />
|
||||
<PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.Enum\CH.Enum.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
25
CH.CryptoStats/CryptoStats.cs
Normal file
25
CH.CryptoStats/CryptoStats.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.CryptoStats.Abstractions;
|
||||
|
||||
public class CryptoStats
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string Symbol { get; set; }
|
||||
public decimal MaxSupply { get; set; }
|
||||
public decimal CirculatingSupply { get; set; }
|
||||
public decimal TotalSupply { get; set; }
|
||||
public required Currency Currency { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Volume24H { get; set; }
|
||||
public decimal VolumeChange24H { get; set; }
|
||||
public decimal MarketCap { get; set; }
|
||||
public decimal MarketCapDominance { get; set; }
|
||||
public decimal FullyDilutedMarketCap { get; set; }
|
||||
}
|
13
CH.CryptoStats/ICryptoStats.cs
Normal file
13
CH.CryptoStats/ICryptoStats.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CH.CryptoStats.Abstractions;
|
||||
|
||||
public interface ICryptoStats
|
||||
{
|
||||
public Task<CryptoStats> GetCryptoStatsAsync(string cryptoName, string currency, CancellationToken cancellationToken);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace CH.Dal.Abstractions
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
6
CH.Dal.Abstractions/IHasId.cs
Normal file
6
CH.Dal.Abstractions/IHasId.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace CH.Dal.Abstractions;
|
||||
|
||||
public interface IHasId<T>
|
||||
{
|
||||
public T Id { get; set; }
|
||||
}
|
@ -17,5 +17,9 @@
|
||||
<PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" />
|
||||
<PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CH.Dal.Abstractions\CH.Dal.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\CH.Enum\CH.Enum.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,18 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CH.Dal.DbEntity;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.Dal;
|
||||
|
||||
public class CHDbContext(DbContextOptions options) : CHDbScaffoldedContext(options)
|
||||
{
|
||||
static CHDbContext() => NpgsqlConnection.GlobalTypeMapper
|
||||
.MapEnum<Currency>("currency")
|
||||
.MapEnum<EnergyRateExceptionThresholdResetType>("energy_rate_exception_threshold_reset_type");
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.HasPostgresEnum<Currency>("currency");
|
||||
modelBuilder.HasPostgresEnum<EnergyRateExceptionThresholdResetType>("energy_rate_exception_threshold_reset_type");
|
||||
|
||||
modelBuilder.Entity<EnergyRate>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Currency)
|
||||
.HasColumnName("currency")
|
||||
.HasColumnType("currency");
|
||||
});
|
||||
modelBuilder.Entity<EnergyRateException>(entity =>
|
||||
{
|
||||
entity.Property(e => e.ResetType)
|
||||
.HasColumnName("reset_type")
|
||||
.HasColumnType("energy_rate_exception_threshold_reset_type");
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
|
@ -16,6 +16,14 @@ public partial class CHDbScaffoldedContext : DbContext
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<EnergyProvider> EnergyProviders { get; set; }
|
||||
|
||||
public virtual DbSet<EnergyRate> EnergyRates { get; set; }
|
||||
|
||||
public virtual DbSet<EnergyRateException> EnergyRateExceptions { get; set; }
|
||||
|
||||
public virtual DbSet<EnergyRateUpdate> EnergyRateUpdates { get; set; }
|
||||
|
||||
public virtual DbSet<Location> Locations { get; set; }
|
||||
|
||||
public virtual DbSet<Machine> Machines { get; set; }
|
||||
@ -27,6 +35,99 @@ public partial class CHDbScaffoldedContext : DbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder
|
||||
.HasPostgresEnum("currency", new[] { "USD", "CAD" })
|
||||
.HasPostgresEnum("energy_rate_exception_threshold_reset_type", new[] { "daily" });
|
||||
|
||||
modelBuilder.Entity<EnergyProvider>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("energy_provider_pkey");
|
||||
|
||||
entity.ToTable("energy_provider");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Active).HasColumnName("active");
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'::text)")
|
||||
.HasColumnName("created_at");
|
||||
entity.Property(e => e.DisabledAt).HasColumnName("disabled_at");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EnergyRate>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("energy_rate_pkey");
|
||||
|
||||
entity.ToTable("energy_rate");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Active).HasColumnName("active");
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'::text)")
|
||||
.HasColumnName("created_at");
|
||||
entity.Property(e => e.DisabledAt).HasColumnName("disabled_at");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.Price).HasColumnName("price");
|
||||
entity.Property(e => e.ProviderId).HasColumnName("provider_id");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
|
||||
entity.HasOne(d => d.Provider).WithMany(p => p.EnergyRates)
|
||||
.HasForeignKey(d => d.ProviderId)
|
||||
.HasConstraintName("energy_rate_provider_id_fkey");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EnergyRateException>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("energy_rate_exception_pkey");
|
||||
|
||||
entity.ToTable("energy_rate_exception");
|
||||
|
||||
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.EndedAt).HasColumnName("ended_at");
|
||||
entity.Property(e => e.EnergyThreshold).HasColumnName("energy_threshold");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.RateId).HasColumnName("rate_id");
|
||||
entity.Property(e => e.StartedAt).HasColumnName("started_at");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
|
||||
entity.HasOne(d => d.Rate).WithMany(p => p.EnergyRateExceptions)
|
||||
.HasForeignKey(d => d.RateId)
|
||||
.HasConstraintName("energy_rate_exception_rate_id_fkey");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EnergyRateUpdate>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("energy_rate_update_pkey");
|
||||
|
||||
entity.ToTable("energy_rate_update");
|
||||
|
||||
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.Rate).HasColumnName("rate");
|
||||
entity.Property(e => e.RateId).HasColumnName("rate_id");
|
||||
entity.Property(e => e.StartedAt).HasColumnName("started_at");
|
||||
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
|
||||
|
||||
entity.HasOne(d => d.RateNavigation).WithMany(p => p.EnergyRateUpdates)
|
||||
.HasForeignKey(d => d.RateId)
|
||||
.HasConstraintName("energy_rate_update_rate_id_fkey");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Location>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("location_pkey");
|
||||
|
8
CH.Dal/DbEntity/EnergyProvider.Extensions.cs
Normal file
8
CH.Dal/DbEntity/EnergyProvider.Extensions.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using CH.Dal.Abstractions;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyProvider : IHasId<long>
|
||||
{
|
||||
|
||||
}
|
21
CH.Dal/DbEntity/EnergyProvider.cs
Normal file
21
CH.Dal/DbEntity/EnergyProvider.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyProvider
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public bool Active { get; set; }
|
||||
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual ICollection<EnergyRate> EnergyRates { get; set; } = new List<EnergyRate>();
|
||||
}
|
11
CH.Dal/DbEntity/EnergyRate.Extension.cs
Normal file
11
CH.Dal/DbEntity/EnergyRate.Extension.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using CH.Dal.Abstractions;
|
||||
using CH.Enum;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyRate : IHasId<long>
|
||||
{
|
||||
[Column(TypeName = "currency")]
|
||||
public Currency Currency { get; set; }
|
||||
}
|
29
CH.Dal/DbEntity/EnergyRate.cs
Normal file
29
CH.Dal/DbEntity/EnergyRate.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyRate
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long? ProviderId { get; set; }
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public decimal? Price { get; set; }
|
||||
|
||||
public bool Active { get; set; }
|
||||
|
||||
public DateTime? DisabledAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual ICollection<EnergyRateException> EnergyRateExceptions { get; set; } = new List<EnergyRateException>();
|
||||
|
||||
public virtual ICollection<EnergyRateUpdate> EnergyRateUpdates { get; set; } = new List<EnergyRateUpdate>();
|
||||
|
||||
public virtual EnergyProvider? Provider { get; set; }
|
||||
}
|
10
CH.Dal/DbEntity/EnergyRateException.Extension.cs
Normal file
10
CH.Dal/DbEntity/EnergyRateException.Extension.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using CH.Dal.Abstractions;
|
||||
using CH.Enum;
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyRateException : IHasId<long>
|
||||
{
|
||||
[Column(TypeName = "energy_rate_exception_threshold_reset_type")]
|
||||
public EnergyRateExceptionThresholdResetType ResetType { get; set; }
|
||||
}
|
25
CH.Dal/DbEntity/EnergyRateException.cs
Normal file
25
CH.Dal/DbEntity/EnergyRateException.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyRateException
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long? RateId { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public decimal? EnergyThreshold { get; set; }
|
||||
|
||||
public DateTime? StartedAt { get; set; }
|
||||
|
||||
public DateTime? EndedAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual EnergyRate? Rate { get; set; }
|
||||
}
|
23
CH.Dal/DbEntity/EnergyRateUpdate.cs
Normal file
23
CH.Dal/DbEntity/EnergyRateUpdate.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CH.Dal.DbEntity;
|
||||
|
||||
public partial class EnergyRateUpdate
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long? RateId { get; set; }
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public DateTime StartedAt { get; set; }
|
||||
|
||||
public decimal? Rate { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public virtual EnergyRate? RateNavigation { get; set; }
|
||||
}
|
25
CH.Dal/Validators/DbEntityExistValidator.cs
Normal file
25
CH.Dal/Validators/DbEntityExistValidator.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using CH.Dal.Abstractions;
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CH.Dal.Validators;
|
||||
|
||||
public class DbEntityExistValidator<TEntity, TValue> : AbstractValidator<TValue>
|
||||
where TEntity : class, IHasId<TValue>
|
||||
{
|
||||
private readonly CHDbContext _dbContext;
|
||||
|
||||
public DbEntityExistValidator(CHDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
|
||||
RuleFor(value => value)
|
||||
.NotNull()
|
||||
.MustAsync(IsEntityExist)
|
||||
.WithMessage("Invalid Entity");
|
||||
}
|
||||
|
||||
private Task<bool> IsEntityExist(TValue id,
|
||||
CancellationToken cancellationToken)
|
||||
=> _dbContext.Set<TEntity>().AnyAsync(entity => entity.Id!.Equals(id), cancellationToken);
|
||||
}
|
9
CH.Energy.Abstractions/CH.Energy.Abstractions.csproj
Normal file
9
CH.Energy.Abstractions/CH.Energy.Abstractions.csproj
Normal file
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
14
CH.Energy.Abstractions/EnergyPeak.cs
Normal file
14
CH.Energy.Abstractions/EnergyPeak.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.Energy.Abstractions;
|
||||
|
||||
public class EnergyPeak
|
||||
{
|
||||
public string? Sector { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
}
|
12
CH.Energy.Abstractions/IEnergyStats.cs
Normal file
12
CH.Energy.Abstractions/IEnergyStats.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.Energy.Abstractions;
|
||||
|
||||
public interface IEnergyStats
|
||||
{
|
||||
public Task<EnergyPeak> GetEnergyPeak(CancellationToken cancellationToken);
|
||||
}
|
9
CH.Energy.HydroQuebec/CH.Energy.HydroQuebec.csproj
Normal file
9
CH.Energy.HydroQuebec/CH.Energy.HydroQuebec.csproj
Normal file
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
11
CH.Enum/CH.Enum.csproj
Normal file
11
CH.Enum/CH.Enum.csproj
Normal file
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
|
||||
</ItemGroup>
|
||||
</Project>
|
10
CH.Enum/Currency.cs
Normal file
10
CH.Enum/Currency.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using NpgsqlTypes;
|
||||
namespace CH.Enum;
|
||||
|
||||
public enum Currency
|
||||
{
|
||||
[PgName("USD")]
|
||||
USD,
|
||||
[PgName("CAD")]
|
||||
CAD,
|
||||
}
|
9
CH.Enum/EnergyRateExceptionThresholdResetType.cs
Normal file
9
CH.Enum/EnergyRateExceptionThresholdResetType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using NpgsqlTypes;
|
||||
|
||||
namespace CH.Enum;
|
||||
|
||||
public enum EnergyRateExceptionThresholdResetType
|
||||
{
|
||||
[PgName("daily")]
|
||||
daily,
|
||||
}
|
13
CH.KeycloakApi/CH.KeycloakApi.csproj
Normal file
13
CH.KeycloakApi/CH.KeycloakApi.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityModel" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
182
CH.KeycloakApi/KeycloakService.cs
Normal file
182
CH.KeycloakApi/KeycloakService.cs
Normal file
@ -0,0 +1,182 @@
|
||||
using System.Text;
|
||||
using IdentityModel.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CH.KeycloakApi;
|
||||
|
||||
public class KeycloakService
|
||||
{
|
||||
private readonly KeycloakSettings settings;
|
||||
|
||||
public KeycloakService(IConfiguration configuration)
|
||||
{
|
||||
this.settings = new KeycloakSettings();
|
||||
configuration.Bind("Keycloak", settings);
|
||||
}
|
||||
|
||||
public KeycloakSettings Settings => settings;
|
||||
|
||||
// use token manager instead
|
||||
public async Task<string> GetTokenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tokenEndpoint = $"{this.settings.Endpoint}/realms/${settings.ApiRealm}/protocol/openid-connect/token";
|
||||
var client = new HttpClient();
|
||||
var response = await client.RequestTokenAsync(new TokenRequest
|
||||
{
|
||||
Address = tokenEndpoint,
|
||||
GrantType = "client_credentials",
|
||||
ClientId = this.settings.ClientId,
|
||||
ClientSecret = this.settings.ClientSecret,
|
||||
}, cancellationToken);
|
||||
|
||||
return response.AccessToken;
|
||||
}
|
||||
|
||||
public async Task<KeycloakUser?> GetUserByEmailAsync(string realm, string email)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?email={email}";
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var response = await httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var keycloakUsers = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
||||
return keycloakUsers?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<KeycloakUser?> GetUserByIdAsync(string realm, string id)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}";
|
||||
var response = await httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var keycloakUser = JsonConvert.DeserializeObject<KeycloakUser>(json);
|
||||
return keycloakUser;
|
||||
}
|
||||
|
||||
public async Task SendChangePasswordEmailAsync(string realm, string id)
|
||||
{
|
||||
//PUT /{realm}/users/{id}/execute-actions-email
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/execute-actions-email";
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var putJson = JsonConvert.SerializeObject(new string[] {
|
||||
"UPDATE_PASSWORD"
|
||||
});
|
||||
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<List<KeycloakUser>?> 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());
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?max={max}";
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
url += $"&search={search}";
|
||||
|
||||
var response = await httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var keycloakUsers = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
||||
return keycloakUsers;
|
||||
}
|
||||
|
||||
public async Task ChangePasswordAsync(string realm, string id, string newPassword, bool temporary)
|
||||
{
|
||||
// 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();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var putJson = JsonConvert.SerializeObject(new {
|
||||
type = "password",
|
||||
temporary,
|
||||
value = newPassword
|
||||
});
|
||||
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task UpdateUserByIdAsync(string realm, string id, string email, string firstName, string lastName, bool enabled)
|
||||
{
|
||||
var user = await GetUserByIdAsync(realm, id);
|
||||
if (user == null)
|
||||
throw new Exception($"no user {email} from on realm {realm}");
|
||||
|
||||
user.Email = email;
|
||||
user.FirstName = firstName;
|
||||
user.LastName = lastName;
|
||||
user.Username = email;
|
||||
user.Enabled = enabled;
|
||||
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}";
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var putJson = JsonConvert.SerializeObject(user);
|
||||
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string realm, string email)
|
||||
{
|
||||
return await this.GetUserByEmailAsync(realm, email) != null;
|
||||
}
|
||||
|
||||
public async Task<KeycloakUser?> CreateOrUpdateAsync(string realm, string email, string firstName, string lastName, bool enabled)
|
||||
{
|
||||
var existingUser = await GetUserByEmailAsync(realm, email);
|
||||
if (existingUser != null)
|
||||
{
|
||||
await UpdateUserByIdAsync(realm, existingUser.Id, email, firstName, lastName, enabled);
|
||||
return await GetUserByEmailAsync(realm, email);
|
||||
}
|
||||
|
||||
return await CreateUserAsync(realm, email, firstName, lastName, enabled);
|
||||
}
|
||||
|
||||
public async Task<KeycloakUser?> 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);
|
||||
|
||||
var user = new KeycloakUser()
|
||||
{
|
||||
CreatedTimestamp = unixTime,
|
||||
Username = email,
|
||||
FirstName = firstName,
|
||||
LastName = lastName,
|
||||
Enabled = enabled,
|
||||
Totp = false,
|
||||
EmailVerified = false,
|
||||
RequiredActions = new List<String>(),
|
||||
Attributes = null,
|
||||
Email = email,
|
||||
NotBefore = 0,
|
||||
Access = new Dictionary<string, object>
|
||||
{
|
||||
{ "manageGroupMembership", true },
|
||||
{ "view", true },
|
||||
{ "mapRoles", true },
|
||||
{ "impersonate", true },
|
||||
{ "manage", true }
|
||||
}
|
||||
};
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
||||
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users";
|
||||
var postJson = JsonConvert.SerializeObject(user);
|
||||
var response = await httpClient.PostAsync(url, new StringContent(postJson, Encoding.UTF8, "application/json"));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var keycloakUser = await GetUserByEmailAsync(realm, email);
|
||||
return keycloakUser;
|
||||
}
|
||||
}
|
10
CH.KeycloakApi/KeycloakSettings.cs
Normal file
10
CH.KeycloakApi/KeycloakSettings.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace CH.KeycloakApi;
|
||||
|
||||
public class KeycloakSettings
|
||||
{
|
||||
public string Endpoint { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
public string ApiRealm { get; set; }
|
||||
public string Realm { get; set; }
|
||||
}
|
46
CH.KeycloakApi/KeycloakUser.cs
Normal file
46
CH.KeycloakApi/KeycloakUser.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace CH.KeycloakApi;
|
||||
|
||||
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<string> RequiredActions { get; set; }
|
||||
|
||||
[JsonProperty("attributes")]
|
||||
public JObject? Attributes { get; set; }
|
||||
|
||||
[JsonProperty("access")]
|
||||
public Dictionary<string, object> Access { get; set; }
|
||||
}
|
@ -11,6 +11,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.CQRS", "CH.CQRS\CH.CQRS.
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.Authority", "CH.Authority\CH.Authority.csproj", "{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.CryptoStats.Abstractions", "CH.CryptoStats\CH.CryptoStats.Abstractions.csproj", "{5AD75A8F-D4C5-4748-8624-FB65EA417E62}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.CryptoStats.CoinMarketCap", "CH.CryptoStats.CoinMarketCap\CH.CryptoStats.CoinMarketCap.csproj", "{E12AC021-8B7E-4451-BB41-59291230E224}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.CryptoStats.CoinGecko", "CH.CryptoStats.CoinGecko\CH.CryptoStats.CoinGecko.csproj", "{AAF92179-CDA7-4711-89C0-E14445B5FFBE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.Energy.Abstractions", "CH.Energy.Abstractions\CH.Energy.Abstractions.csproj", "{ED45DC6D-75E8-449E-BB24-867C37439ADA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.Energy.HydroQuebec", "CH.Energy.HydroQuebec\CH.Energy.HydroQuebec.csproj", "{91BB50CF-87B6-40A0-BB70-B42B7208167A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.Enum", "CH.Enum\CH.Enum.csproj", "{45E17ADC-A1C9-4EE0-BA6E-A4B52F0621BD}"
|
||||
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
|
||||
@ -33,6 +49,38 @@ Global
|
||||
{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5AD75A8F-D4C5-4748-8624-FB65EA417E62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5AD75A8F-D4C5-4748-8624-FB65EA417E62}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5AD75A8F-D4C5-4748-8624-FB65EA417E62}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5AD75A8F-D4C5-4748-8624-FB65EA417E62}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E12AC021-8B7E-4451-BB41-59291230E224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E12AC021-8B7E-4451-BB41-59291230E224}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E12AC021-8B7E-4451-BB41-59291230E224}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E12AC021-8B7E-4451-BB41-59291230E224}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AAF92179-CDA7-4711-89C0-E14445B5FFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AAF92179-CDA7-4711-89C0-E14445B5FFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AAF92179-CDA7-4711-89C0-E14445B5FFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AAF92179-CDA7-4711-89C0-E14445B5FFBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ED45DC6D-75E8-449E-BB24-867C37439ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ED45DC6D-75E8-449E-BB24-867C37439ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED45DC6D-75E8-449E-BB24-867C37439ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED45DC6D-75E8-449E-BB24-867C37439ADA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{91BB50CF-87B6-40A0-BB70-B42B7208167A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{91BB50CF-87B6-40A0-BB70-B42B7208167A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{91BB50CF-87B6-40A0-BB70-B42B7208167A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{91BB50CF-87B6-40A0-BB70-B42B7208167A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{45E17ADC-A1C9-4EE0-BA6E-A4B52F0621BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{45E17ADC-A1C9-4EE0-BA6E-A4B52F0621BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{45E17ADC-A1C9-4EE0-BA6E-A4B52F0621BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{45E17ADC-A1C9-4EE0-BA6E-A4B52F0621BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{28648B65-7D84-460A-993E-0F98D94CECFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
|
Loading…
Reference in New Issue
Block a user