add cryptostats abstraction, add icryptostats implementation for coinmarketcap, add cryptostat object, add command test icryptostats implementation

This commit is contained in:
DavidGudEnough 2025-01-06 15:17:59 -05:00
parent f8829cd8bd
commit 46911ceaa1
Signed by: david.nguyen
GPG Key ID: 0B95DC36355BEB37
19 changed files with 258 additions and 5 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

@ -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;
@ -39,10 +40,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();

View File

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

View File

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

@ -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;
@ -13,6 +14,7 @@ public class CommandModule : IModule
public IServiceCollection ConfigureServices(IServiceCollection services) public IServiceCollection ConfigureServices(IServiceCollection services)
{ {
services.AddUserCommand(); services.AddUserCommand();
services.AddCryptoCommand();
return services; return services;
} }

View File

@ -0,0 +1,16 @@
using CH.CryptoStats.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CH.CQRS.Service.CryptoStats;
public class CryptoService(CoinMarketCap coinMarketCap)
{
public async Task GetCryptoStatsAsync(CancellationToken cancellationToken)
{
var crypto = await coinMarketCap.GetCryptoStats("bitcoin", "CAD", cancellationToken);
}
}

View File

@ -1,4 +1,6 @@
using CH.CQRS.Service.User; using CH.CQRS.Service.CryptoStats;
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 +10,7 @@ public class SharedModule : IModule
public IServiceCollection ConfigureServices(IServiceCollection services) public IServiceCollection ConfigureServices(IServiceCollection services)
{ {
services.AddScoped<UserService>(); services.AddScoped<UserService>();
services.AddScoped<CryptoService>();
return services; return services;
} }
} }

View File

@ -0,0 +1,15 @@
<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>
</Project>

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CH.CryptoStats.Abstractions;
namespace CH.CryptoStats.CoinGecko;
public class CoinGecko : ICryptoStats
{
public Task<Abstractions.CryptoStats> GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<Abstractions.CryptoStats> ParseCryptoStats(HttpResponseMessage result, string currency, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}

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 AbstractionModule : IModule
{
public IServiceCollection ConfigureServices(IServiceCollection services)
{
services.AddScoped<CoinGecko>();
return services;
}
}

View File

@ -0,0 +1,15 @@
<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>
</Project>

View File

@ -0,0 +1,42 @@
using CH.CryptoStats.Abstractions;
using Microsoft.Extensions.Configuration;
using System.Text.Json;
namespace CH.CryptoStats.CoinMarketCap;
public class CoinMarketCap(HttpClient httpClient, IConfiguration configuration) : ICryptoStats
{
public Task<Abstractions.CryptoStats> GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken)
{
var apiKey = configuration.GetValue<string>("CoinMarketCap:ApiKey");
var apiUrl = configuration.GetValue<string>("CoinMarketCap:ApiUrl");
var result = httpClient.GetAsync($"{apiUrl}slug={cryptoName}&convert={currency}&CMC_PRO_API_KEY={apiKey}",cancellationToken).Result;
return ParseCryptoStats(result, currency, cancellationToken);
}
public Task<Abstractions.CryptoStats> ParseCryptoStats(HttpResponseMessage result, string currency, CancellationToken cancellationToken)
{
var jsonResponse = result.Content.ReadAsStringAsync(cancellationToken);
var jsonDocument = JsonDocument.Parse(jsonResponse.Result);
var data = jsonDocument.RootElement.GetProperty("data");
var cryptoElement = data.GetProperty("1");
var quoteElement = cryptoElement.GetProperty("quote").GetProperty("CAD");
var cryptoStats = new Abstractions.CryptoStats
{
Name = cryptoElement.GetProperty("name").GetString(),
Symbol = cryptoElement.GetProperty("symbol").GetString(),
MaxSupply = cryptoElement.GetProperty("max_supply").GetInt32(),
CirculatingSupply = cryptoElement.GetProperty("circulating_supply").GetInt32(),
TotalSupply = cryptoElement.GetProperty("total_supply").GetInt32(),
Currency = currency,
UpdatedAt = DateTime.Parse(quoteElement .GetProperty("last_updated").GetString()),
Price = quoteElement .GetProperty("price").GetDecimal(),
Volume24H = quoteElement .GetProperty("volume_24h").GetDecimal(),
VolumeChange24H = quoteElement .GetProperty("volume_change_24h").GetDecimal(),
MarketCap = quoteElement .GetProperty("market_cap").GetDecimal(),
MarketCapDominance = quoteElement .GetProperty("market_cap_dominance").GetDecimal(),
FullyDilutedMarketCap = quoteElement .GetProperty("fully_diluted_market_cap").GetDecimal()
};
return Task.FromResult(cryptoStats);
}
}

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 AbstractionModule : IModule
{
public IServiceCollection ConfigureServices(IServiceCollection services)
{
services.AddScoped<CoinMarketCap>();
return services;
}
}

View File

@ -0,0 +1,15 @@
<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>
</Project>

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CH.CryptoStats.Abstractions;
public class CryptoStats
{
public required string Name { get; set; }
public required string Symbol { get; set; }
public int MaxSupply { get; set; }
public int CirculatingSupply { get; set; }
public int TotalSupply { get; set; }
public required string 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.Threading.Tasks;
namespace CH.CryptoStats.Abstractions;
public interface ICryptoStats
{
public Task<CryptoStats> GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken);
public Task<CryptoStats> ParseCryptoStats(HttpResponseMessage result, string currency, CancellationToken cancellationToken);
}

View File

@ -11,6 +11,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.CryptoStats.Abstractions", "CH.CryptoStats\CH.CryptoStats.Abstractions.csproj", "{5AD75A8F-D4C5-4748-8624-FB65EA417E62}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -33,6 +35,10 @@ 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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE