Compare commits

...

9 Commits

75 changed files with 1899 additions and 19 deletions

View File

@ -2,6 +2,9 @@
using CH.CQRS; using CH.CQRS;
using PoweredSoft.Module.Abstractions; using PoweredSoft.Module.Abstractions;
using CH.Dal; using CH.Dal;
using CH.CryptoStats.CoinMarketCap;
using CH.CryptoStats.CoinGecko;
namespace CH.Api; namespace CH.Api;
@ -13,7 +16,8 @@ public class AppModule : IModule
services.AddModule<SharedModule>(); services.AddModule<SharedModule>();
services.AddModule<CommandModule>(); services.AddModule<CommandModule>();
services.AddModule<QueryModule>(); services.AddModule<QueryModule>();
services.AddModule<CoinMarketCapModule>();
services.AddModule<CoinGeckoModule>();
services.AddModule<AuthorityModule>(); services.AddModule<AuthorityModule>();
return services; return services;

View File

@ -34,6 +34,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CH.Authority\CH.Authority.csproj" /> <ProjectReference Include="..\CH.Authority\CH.Authority.csproj" />
<ProjectReference Include="..\CH.CQRS\CH.CQRS.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" /> <ProjectReference Include="..\CH.Dal\CH.Dal.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,4 +1,5 @@
using CH.Api; using CH.Api;
using CH.CryptoStats.Abstractions;
using CH.Dal; using CH.Dal;
using FluentValidation.AspNetCore; using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
@ -16,6 +17,7 @@ using PoweredSoft.Data.EntityFrameworkCore;
using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery;
using PoweredSoft.Module.Abstractions; using PoweredSoft.Module.Abstractions;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CH.Enum;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -39,10 +41,9 @@ builder.Services.AddDefaultCommandDiscovery();
builder.Services.AddDefaultQueryDiscovery(); builder.Services.AddDefaultQueryDiscovery();
builder.Services.AddFluentValidation(); builder.Services.AddFluentValidation();
builder.Services.AddModule<AppModule>(); builder.Services.AddModule<AppModule>();
builder.Services.AddHttpClient();
builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultCommandDiscovery();
builder.Services.AddDefaultQueryDiscovery(); builder.Services.AddDefaultQueryDiscovery();
if (builder.Configuration.GetValue<bool>("Swagger:Enable")) if (builder.Configuration.GetValue<bool>("Swagger:Enable"))
{ {
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
@ -110,7 +111,8 @@ mvcBuilder
var connectionString = builder.Configuration.GetSection("Database").GetValue<string>("ConnectionString"); var connectionString = builder.Configuration.GetSection("Database").GetValue<string>("ConnectionString");
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.MapEnum<Currency>("currency");
dataSourceBuilder.MapEnum<EnergyRateExceptionThresholdResetType>("energy_rate_exception_threshold_reset_type");
var dataSource = dataSourceBuilder.Build(); var dataSource = dataSourceBuilder.Build();
builder.Services.AddDbContextPool<CHDbContext>(options => builder.Services.AddDbContextPool<CHDbContext>(options =>

View File

@ -18,5 +18,19 @@
"TokenUrl": "", "TokenUrl": "",
"ClientId": "", "ClientId": "",
"ClientSecret": "" "ClientSecret": ""
},
"CoinMarketCap": {
"ApiUrl": "",
"ApiKey": ""
},
"CoinGecko": {
"ApiUrl": "",
"ApiKey": ""
},
"HydroQuebec": {
"ApiUrl": ""
} }
} }

View File

@ -16,6 +16,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CH.Authority\CH.Authority.csproj" /> <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" /> <ProjectReference Include="..\CH.Dal\CH.Dal.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View 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()
{
}
}

View 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;
}
}

View 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.");
}
}

View 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.");
}
}

View 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.");
}
}

View 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.");
}
}

View 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.");
}
}

View 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.");
}
}

View 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.");
}
}

View 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;
}
}

View 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));
}
}

View 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);
}
}

View 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);
}
}

View File

@ -1,4 +1,5 @@
using CH.CQRS.Command.User; using CH.CQRS.Command.CryptoStats;
using CH.CQRS.Command.User;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.Module.Abstractions; using PoweredSoft.Module.Abstractions;
using System; using System;
@ -6,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using CH.CQRS.Command.Energy;
namespace CH.CQRS; namespace CH.CQRS;
public class CommandModule : IModule public class CommandModule : IModule
@ -13,7 +15,8 @@ public class CommandModule : IModule
public IServiceCollection ConfigureServices(IServiceCollection services) public IServiceCollection ConfigureServices(IServiceCollection services)
{ {
services.AddUserCommand(); services.AddUserCommand();
services.AddCryptoCommand();
services.AddEnergyCommand();
return services; return services;
} }
} }

View 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;
}
}

View File

@ -0,0 +1,6 @@
namespace CH.CQRS.Query.EnergyProvider;
public class EnergyProviderQueryResult
{
public required List<Dal.DbEntity.EnergyProvider> Data { get; set; }
}

View 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;
}
}

View File

@ -0,0 +1,6 @@
namespace CH.CQRS.Query.EnergyRate;
public class EnergyRateQueryResult
{
public required List<Dal.DbEntity.EnergyRate> Data { get; set; }
}

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
namespace CH.CQRS.Query.EnergyRateException;
public class EnergyRateExceptionQueryResult
{
public required List<Dal.DbEntity.EnergyRateException> Data { get; set; }
}

View File

@ -7,6 +7,9 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using CH.CQRS.Query.EnergyProvider;
using CH.CQRS.Query.EnergyRate;
using CH.CQRS.Query.EnergyRateException;
namespace CH.CQRS; namespace CH.CQRS;
public class QueryModule : IModule public class QueryModule : IModule
@ -14,6 +17,9 @@ public class QueryModule : IModule
public IServiceCollection ConfigureServices(IServiceCollection services) public IServiceCollection ConfigureServices(IServiceCollection services)
{ {
services.AddQuery<HealthQuery, HealthQueryResult, HealthQueryHandler>(); services.AddQuery<HealthQuery, HealthQueryResult, HealthQueryHandler>();
services.AddQuery<EnergyProviderQuery, EnergyProviderQueryResult, EnergyProviderQueryHandler>();
services.AddQuery<EnergyRateQuery, EnergyRateQueryResult, EnergyRateQueryHandler>();
services.AddQuery<EnergyRateExceptionQuery, EnergyRateExceptionQueryResult, EnergyRateExceptionQueryHandler>();
return services; return services;
} }
} }

View 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);
}
}

View 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);
}
}
}

View File

@ -0,0 +1,7 @@
namespace CH.CQRS.Service.Energy.Options;
public class CreateEnergyProviderCommandOptions
{
public required string Name { get; set; }
public bool Active { get; set; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -0,0 +1,7 @@
namespace CH.CQRS.Service.Energy.Options;
public class DisableEnergyProviderCommandOptions
{
public long ProviderId { get; set; }
public DateTime? DisabledAt { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace CH.CQRS.Service.Energy.Options;
public class DisableEnergyRateCommandOptions
{
public long RateId { get; set; }
public DateTime? DisabledAt { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace CH.CQRS.Service.Energy.Options;
public class EnableEnergyProviderCommandOptions
{
public long ProviderId { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace CH.CQRS.Command.Energy;
public class EnableEnergyRateCommandOptions
{
public long RateId { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace CH.CQRS.Service.Energy.Options;
public class UpdateEnergyProviderCommandOptions
{
public long ProviderId { get; set; }
public required string Name { get; set; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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 Microsoft.Extensions.DependencyInjection;
using PoweredSoft.Module.Abstractions; using PoweredSoft.Module.Abstractions;
@ -8,6 +11,8 @@ public class SharedModule : IModule
public IServiceCollection ConfigureServices(IServiceCollection services) public IServiceCollection ConfigureServices(IServiceCollection services)
{ {
services.AddScoped<UserService>(); services.AddScoped<UserService>();
services.AddScoped<CryptoService>();
services.AddScoped<EnergyService>();
return services; return services;
} }
} }

View 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>

View 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; }
}

View 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;
}
}

View 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);
}
}

View File

@ -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>

View 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; }
}

View 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;
}
}

View 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);
}
}

View 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>

View 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; }
}

View 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);
}

View File

@ -1,7 +0,0 @@
namespace CH.Dal.Abstractions
{
public class Class1
{
}
}

View File

@ -0,0 +1,6 @@
namespace CH.Dal.Abstractions;
public interface IHasId<T>
{
public T Id { get; set; }
}

View File

@ -17,5 +17,9 @@
<PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" /> <PackageReference Include="PoweredSoft.Data.Core" Version="3.0.0" />
<PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" /> <PackageReference Include="PoweredSoft.Module.Abstractions" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CH.Dal.Abstractions\CH.Dal.Abstractions.csproj" />
<ProjectReference Include="..\CH.Enum\CH.Enum.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,18 +1,33 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql; using Npgsql;
using System; using CH.Dal.DbEntity;
using System.Collections.Generic; using CH.Enum;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CH.Dal; namespace CH.Dal;
public class CHDbContext(DbContextOptions options) : CHDbScaffoldedContext(options) 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) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(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) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

View File

@ -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<Location> Locations { get; set; }
public virtual DbSet<Machine> Machines { get; set; } public virtual DbSet<Machine> Machines { get; set; }
@ -27,6 +35,99 @@ public partial class CHDbScaffoldedContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) 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 => modelBuilder.Entity<Location>(entity =>
{ {
entity.HasKey(e => e.Id).HasName("location_pkey"); entity.HasKey(e => e.Id).HasName("location_pkey");

View File

@ -0,0 +1,8 @@
using CH.Dal.Abstractions;
namespace CH.Dal.DbEntity;
public partial class EnergyProvider : IHasId<long>
{
}

View 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>();
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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);
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View 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; }
}

View 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);
}

View 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
View 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
View File

@ -0,0 +1,10 @@
using NpgsqlTypes;
namespace CH.Enum;
public enum Currency
{
[PgName("USD")]
USD,
[PgName("CAD")]
CAD,
}

View File

@ -0,0 +1,9 @@
using NpgsqlTypes;
namespace CH.Enum;
public enum EnergyRateExceptionThresholdResetType
{
[PgName("daily")]
daily,
}

View 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>

View 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;
}
}

View 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; }
}

View 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; }
}

View File

@ -11,6 +11,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.CQRS", "CH.CQRS\CH.CQRS.
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.Authority", "CH.Authority\CH.Authority.csproj", "{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CH.Authority", "CH.Authority\CH.Authority.csproj", "{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{2B26A5F6-B78D-418F-A28D-80E7E9CD5428}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE