diff --git a/CH.Api/AppModule.cs b/CH.Api/AppModule.cs index d4c9d71..a31b361 100644 --- a/CH.Api/AppModule.cs +++ b/CH.Api/AppModule.cs @@ -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(); services.AddModule(); services.AddModule(); - + services.AddModule(); + services.AddModule(); services.AddModule(); return services; diff --git a/CH.Api/Program.cs b/CH.Api/Program.cs index ea8462f..fb0e6c3 100644 --- a/CH.Api/Program.cs +++ b/CH.Api/Program.cs @@ -1,4 +1,5 @@ using CH.Api; +using CH.CryptoStats.Abstractions; using CH.Dal; using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -39,10 +40,9 @@ builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); builder.Services.AddFluentValidation(); builder.Services.AddModule(); - +builder.Services.AddHttpClient(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); - if (builder.Configuration.GetValue("Swagger:Enable")) { builder.Services.AddEndpointsApiExplorer(); diff --git a/CH.Api/appsettings.json b/CH.Api/appsettings.json index 447c4aa..f9c8192 100644 --- a/CH.Api/appsettings.json +++ b/CH.Api/appsettings.json @@ -18,5 +18,10 @@ "TokenUrl": "", "ClientId": "", "ClientSecret": "" + }, + "CoinMarketCap": { + "ApiUrl": "", + "ApiKey": "" + } } diff --git a/CH.CQRS/CH.CQRS.csproj b/CH.CQRS/CH.CQRS.csproj index 86fb279..e293ada 100644 --- a/CH.CQRS/CH.CQRS.csproj +++ b/CH.CQRS/CH.CQRS.csproj @@ -16,6 +16,7 @@ + diff --git a/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs b/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs new file mode 100644 index 0000000..8092c36 --- /dev/null +++ b/CH.CQRS/Command/CryptoStats/GetCryptoStatsCommand.cs @@ -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 +{ + public Task HandleAsync(GetCryptoStatsCommand command, CancellationToken cancellationToken = default) + { + return cryptoService.GetCryptoStatsAsync(cancellationToken); + } +} +public class GetCryptoStatsCommandValidator : AbstractValidator +{ + public GetCryptoStatsCommandValidator() + { + } +} \ No newline at end of file diff --git a/CH.CQRS/Command/CryptoStats/ServiceCollectionExtension.cs b/CH.CQRS/Command/CryptoStats/ServiceCollectionExtension.cs new file mode 100644 index 0000000..1ecb89e --- /dev/null +++ b/CH.CQRS/Command/CryptoStats/ServiceCollectionExtension.cs @@ -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(); + return services; + } +} diff --git a/CH.CQRS/CommandModule.cs b/CH.CQRS/CommandModule.cs index cbc143d..412fca9 100644 --- a/CH.CQRS/CommandModule.cs +++ b/CH.CQRS/CommandModule.cs @@ -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; @@ -13,6 +14,7 @@ public class CommandModule : IModule public IServiceCollection ConfigureServices(IServiceCollection services) { services.AddUserCommand(); + services.AddCryptoCommand(); return services; } diff --git a/CH.CQRS/Service/CryptoStats/CryptoService.cs b/CH.CQRS/Service/CryptoStats/CryptoService.cs new file mode 100644 index 0000000..31c6b44 --- /dev/null +++ b/CH.CQRS/Service/CryptoStats/CryptoService.cs @@ -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); + } +} diff --git a/CH.CQRS/SharedModule.cs b/CH.CQRS/SharedModule.cs index c2f4909..2dbbf45 100644 --- a/CH.CQRS/SharedModule.cs +++ b/CH.CQRS/SharedModule.cs @@ -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 PoweredSoft.Module.Abstractions; @@ -8,6 +10,7 @@ public class SharedModule : IModule public IServiceCollection ConfigureServices(IServiceCollection services) { services.AddScoped(); + services.AddScoped(); return services; } } diff --git a/CH.CryptoStats.CoinGecko/CH.CryptoStats.CoinGecko.csproj b/CH.CryptoStats.CoinGecko/CH.CryptoStats.CoinGecko.csproj new file mode 100644 index 0000000..c9141d6 --- /dev/null +++ b/CH.CryptoStats.CoinGecko/CH.CryptoStats.CoinGecko.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/CH.CryptoStats.CoinGecko/CoinGecko.cs b/CH.CryptoStats.CoinGecko/CoinGecko.cs new file mode 100644 index 0000000..9554caf --- /dev/null +++ b/CH.CryptoStats.CoinGecko/CoinGecko.cs @@ -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 GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task ParseCryptoStats(HttpResponseMessage result, string currency, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} diff --git a/CH.CryptoStats.CoinGecko/CoinGeckoModule.cs b/CH.CryptoStats.CoinGecko/CoinGeckoModule.cs new file mode 100644 index 0000000..e652d06 --- /dev/null +++ b/CH.CryptoStats.CoinGecko/CoinGeckoModule.cs @@ -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(); + return services; + } +} diff --git a/CH.CryptoStats.CoinMarketCap/CH.CryptoStats.CoinMarketCap.csproj b/CH.CryptoStats.CoinMarketCap/CH.CryptoStats.CoinMarketCap.csproj new file mode 100644 index 0000000..c9141d6 --- /dev/null +++ b/CH.CryptoStats.CoinMarketCap/CH.CryptoStats.CoinMarketCap.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/CH.CryptoStats.CoinMarketCap/CoinMarketCap.cs b/CH.CryptoStats.CoinMarketCap/CoinMarketCap.cs new file mode 100644 index 0000000..4a976ff --- /dev/null +++ b/CH.CryptoStats.CoinMarketCap/CoinMarketCap.cs @@ -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 GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken) + { + var apiKey = configuration.GetValue("CoinMarketCap:ApiKey"); + var apiUrl = configuration.GetValue("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 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); + } +} diff --git a/CH.CryptoStats.CoinMarketCap/CoinMarketCapModule.cs b/CH.CryptoStats.CoinMarketCap/CoinMarketCapModule.cs new file mode 100644 index 0000000..fee2a27 --- /dev/null +++ b/CH.CryptoStats.CoinMarketCap/CoinMarketCapModule.cs @@ -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(); + return services; + } +} diff --git a/CH.CryptoStats/CH.CryptoStats.Abstractions.csproj b/CH.CryptoStats/CH.CryptoStats.Abstractions.csproj new file mode 100644 index 0000000..c9141d6 --- /dev/null +++ b/CH.CryptoStats/CH.CryptoStats.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/CH.CryptoStats/CryptoStats.cs b/CH.CryptoStats/CryptoStats.cs new file mode 100644 index 0000000..c815e19 --- /dev/null +++ b/CH.CryptoStats/CryptoStats.cs @@ -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; } +} \ No newline at end of file diff --git a/CH.CryptoStats/ICryptoStats.cs b/CH.CryptoStats/ICryptoStats.cs new file mode 100644 index 0000000..6edce7e --- /dev/null +++ b/CH.CryptoStats/ICryptoStats.cs @@ -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 GetCryptoStats(string cryptoName, string currency, CancellationToken cancellationToken); + public Task ParseCryptoStats(HttpResponseMessage result, string currency, CancellationToken cancellationToken); +} diff --git a/ConstellationHeating.sln b/ConstellationHeating.sln index 39610b9..483c20a 100644 --- a/ConstellationHeating.sln +++ b/ConstellationHeating.sln @@ -11,6 +11,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CH.CryptoStats.Abstractions", "CH.CryptoStats\CH.CryptoStats.Abstractions.csproj", "{5AD75A8F-D4C5-4748-8624-FB65EA417E62}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE