From 65f307bbf3eb572dc9be3c91b45bf6f4c1b5a0da Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sat, 25 Nov 2023 14:06:32 -0600 Subject: [PATCH] initial commit --- .editorconfig | 12 ++ .github/dependabot.yml | 11 ++ .github/workflows/main_g2to3.yml | 57 ++++++++ .vscode/launch.json | 41 ++++++ .vscode/settings.json | 5 + .vscode/tasks.json | 41 ++++++ README.md | 3 +- Web2Gateway.sln | 25 ++++ Web2Gateway/Api.cs | 151 +++++++++++++++++++++ Web2Gateway/ChiaConfig.cs | 56 ++++++++ Web2Gateway/G2To3Service.cs | 102 ++++++++++++++ Web2Gateway/HexUtils.cs | 32 +++++ Web2Gateway/Program.cs | 57 ++++++++ Web2Gateway/Properties/launchSettings.json | 41 ++++++ Web2Gateway/Utils.cs | 41 ++++++ Web2Gateway/Web2Gateway.csproj | 21 +++ Web2Gateway/appsettings.Development.json | 23 ++++ Web2Gateway/appsettings.json | 30 ++++ 18 files changed, 747 insertions(+), 2 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/main_g2to3.yml create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Web2Gateway.sln create mode 100644 Web2Gateway/Api.cs create mode 100644 Web2Gateway/ChiaConfig.cs create mode 100644 Web2Gateway/G2To3Service.cs create mode 100644 Web2Gateway/HexUtils.cs create mode 100644 Web2Gateway/Program.cs create mode 100644 Web2Gateway/Properties/launchSettings.json create mode 100644 Web2Gateway/Utils.cs create mode 100644 Web2Gateway/Web2Gateway.csproj create mode 100644 Web2Gateway/appsettings.Development.json create mode 100644 Web2Gateway/appsettings.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7d144ec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fb5f8df --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/Web2Gateway" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/main_g2to3.yml b/.github/workflows/main_g2to3.yml new file mode 100644 index 0000000..79e26ef --- /dev/null +++ b/.github/workflows/main_g2to3.yml @@ -0,0 +1,57 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy ASP.Net Core app to Azure Web App - g2to3 + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '7.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: .net-app + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'g2to3' + slot-name: 'Production' + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_1FC60C31816144F3BEBD557C2BD0ED69 }} + package: . diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0fbcf80 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Web2Gateway/bin/Debug/net8.0/Web2Gateway.dll", + "args": [ + //"C:\\Users\\don\\.g2to3\\appsettings.json" + ], + "cwd": "${workspaceFolder}/Web2Gateway", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+http://\\S+:([0-9]+)", + "uriFormat": "http://localhost:%s" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a8f6a58 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "referer" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f142a3f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Web2Gateway.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Web2Gateway.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Web2Gateway.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 45580a1..551f8b0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# Web2Gateway -chia data layer web2 gateway +# Chia Web2Gateway diff --git a/Web2Gateway.sln b/Web2Gateway.sln new file mode 100644 index 0000000..e2857ae --- /dev/null +++ b/Web2Gateway.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web2Gateway", "Web2Gateway\Web2Gateway.csproj", "{DECF587E-5841-468D-AC62-F418562C8E36}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DECF587E-5841-468D-AC62-F418562C8E36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DECF587E-5841-468D-AC62-F418562C8E36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DECF587E-5841-468D-AC62-F418562C8E36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DECF587E-5841-468D-AC62-F418562C8E36}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B274398D-3BFC-465C-95C0-D55C1DD4A194} + EndGlobalSection +EndGlobal diff --git a/Web2Gateway/Api.cs b/Web2Gateway/Api.cs new file mode 100644 index 0000000..424e283 --- /dev/null +++ b/Web2Gateway/Api.cs @@ -0,0 +1,151 @@ +using Web2Gateway; +using System.Text.RegularExpressions; + +internal static class Api +{ + public static WebApplication ConfigureApi(this WebApplication app, ILogger logger) + { + app.MapGet("/", () => Results.Redirect("/.well-known", true)); + app.MapGet("/.well-known", () => + { + var g223 = app.Services.GetRequiredService(); + return g223.GetWellKnown(); + }) + .WithName(".well-known") + .WithOpenApi(); + + app.MapGet("/{storeId}", async (HttpContext httpContext, string storeId, bool? showKeys, CancellationToken cancellationToken) => + { + try + { + storeId = storeId.TrimEnd('/'); + + // A referrer indicates that the user is trying to access the store from a website + // we want to redirect them so that the URL includes the storeId in the path + var referer = httpContext.Request.Headers["referer"].ToString(); + if (!string.IsNullOrEmpty(referer) && referer.Contains(storeId)) + { + httpContext.Response.Headers["Location"] = $"{referer}/{storeId}"; + return Results.Redirect($"{referer}/{storeId}", true); + } + + var g223 = app.Services.GetRequiredService(); + var keys = await g223.GetKeys(storeId, cancellationToken); + + if (keys is not null) + { + var decodedKeys = keys.Select(key => HexUtils.FromHex(key)).ToList(); + + // the key represents a SPA app, so we want to return the index.html + if (decodedKeys != null && decodedKeys.Count > 0 && decodedKeys.Contains("index.html") && showKeys != true) + { + var html = await g223.GetValueAsHtml(storeId, cancellationToken); + return Results.Content(html, "text/html"); + } + + return Results.Ok(decodedKeys); + } + + return Results.NotFound(); + } + catch (InvalidOperationException ex) + { + logger.LogError(ex, "{Message}", ex.Message); + return Results.StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception ex) + { + logger.LogError(ex, "{Message}", ex.Message); + return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); + } + }) + .WithName("{storeId}") + .WithOpenApi(); + + app.MapGet("/{storeId}/{*catchAll}", async (HttpContext httpContext, string storeId, string catchAll, CancellationToken cancellationToken) => + { + try + { + var key = catchAll; + // Remove everything after the first '#' + if (key.Contains('#')) + { + key = key.Split('#')[0]; + } + key = key.TrimEnd('/'); + + // A referrer indicates that the user is trying to access the store from a website + // we want to redirect them so that the URL includes the storeId in the path + var referer = httpContext.Request.Headers["referer"].ToString(); + if (!string.IsNullOrEmpty(referer) && !referer.Contains(storeId)) + { + key = key.TrimStart('/'); + httpContext.Response.Headers["Location"] = $"{referer}/{storeId}/{key}"; + + return Results.Redirect($"{referer}/{storeId}/{key}", true); + } + + var hexKey = HexUtils.ToHex(key); + var g223 = app.Services.GetRequiredService(); + var rawValue = await g223.GetValue(storeId, hexKey, cancellationToken); + if (rawValue is null) + { + Console.WriteLine($"couldn't find: {key}"); + + return Results.NotFound(); + } + Console.WriteLine($"found: {key}"); + var decodedValue = HexUtils.FromHex(rawValue); + var fileExtension = Path.GetExtension(key); + + if (Utils.TryParseJson(decodedValue, out var json) && json?.type == "multipart") + { + string mimeType = Utils.GetMimeType(fileExtension) ?? "application/octet-stream"; + var bytes = await g223.GetValuesAsBytes(storeId, json, cancellationToken); + + return Results.File(bytes, mimeType); + } + else if (!string.IsNullOrEmpty(fileExtension)) + { + string mimeType = Utils.GetMimeType(fileExtension) ?? "application/octet-stream"; + + return Results.File(Convert.FromHexString(rawValue), mimeType); + } + else if (json is not null) + { + return Results.Ok(json); + } + else if (Utils.IsBase64Image(decodedValue)) + { + // figure out the mime type + var regex = new Regex(@"[^:]\w+\/[\w-+\d.]+(?=;|,)"); + var match = regex.Match(decodedValue); + + // convert the base64 string to a byte array + string base64Image = decodedValue.Split(";base64,")[^1]; + byte[] imageBuffer = Convert.FromBase64String(base64Image); + + return Results.File(imageBuffer, match.Value); + } + else + { + return Results.Content(decodedValue); + } + } + catch (InvalidOperationException ex) + { + logger.LogError(ex, "{Message}", ex.Message); + return Results.StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception ex) + { + logger.LogError(ex, "{Message}", ex.Message); + return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); + } + }) + .WithName("{storeId}/*") + .WithOpenApi(); + + return app; + } +} diff --git a/Web2Gateway/ChiaConfig.cs b/Web2Gateway/ChiaConfig.cs new file mode 100644 index 0000000..f7aee39 --- /dev/null +++ b/Web2Gateway/ChiaConfig.cs @@ -0,0 +1,56 @@ +using chia.dotnet; + +namespace Web2Gateway; + +/// +/// Provides methods for interacting with the Chia blockchain. +/// +public sealed class ChiaConfig +{ + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public ChiaConfig(ILogger logger, IConfiguration configuration) => + (_logger, _configuration) = (logger, configuration); + + private Config GetConfig() + { + // first see if we have a config file path in the appsettings.json + var configPath = _configuration.GetValue("App:chia_config_path", ""); + if (!string.IsNullOrEmpty(configPath)) + { + _logger.LogInformation("Using chia config {configPath}", configPath); + + return Config.Open(configPath); + } + + // if not use the chia default '~/.chia/mainnet/config/config.yaml' + _logger.LogInformation("Using default chia config"); + + return Config.Open(); + } + + public EndpointInfo GetDataLayerEndpoint() + { + // first check user secrets for the data_layer connection + // https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=windows + var dataLayerUri = _configuration.GetValue("data_layer_uri", "")!; + if (!string.IsNullOrEmpty(dataLayerUri)) + { + _logger.LogInformation("Connecting to {dataLayerUri}", dataLayerUri); + return new EndpointInfo() + { + Uri = new Uri(dataLayerUri), + // when stored in an environment variable the newlines might be escaped + Cert = _configuration.GetValue("data_layer_cert", "")!.Replace("\\n", "\n"), + Key = _configuration.GetValue("data_layer_key", "")!.Replace("\\n", "\n") + }; + } + else + { + // if not present see if we can get it from the config file + return GetConfig().GetEndpoint("data_layer"); + } + } +} + diff --git a/Web2Gateway/G2To3Service.cs b/Web2Gateway/G2To3Service.cs new file mode 100644 index 0000000..6141119 --- /dev/null +++ b/Web2Gateway/G2To3Service.cs @@ -0,0 +1,102 @@ +using Microsoft.Extensions.Caching.Memory; + +using chia.dotnet; + +namespace Web2Gateway; + + +public record WellKnown() +{ + public string xch_address { get; init; } = ""; + public string donation_address { get; init; } = ""; +} + +public sealed class G2To3Service +{ + private readonly DataLayerProxy _dataLayer; + private readonly IMemoryCache _memoryCache; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public G2To3Service(DataLayerProxy dataLayer, IMemoryCache memoryCache, ILogger logger, IConfiguration configuration) => + (_dataLayer, _memoryCache, _logger, _configuration) = (dataLayer, memoryCache, logger, configuration); + + public WellKnown GetWellKnown() + { + return new WellKnown + { + xch_address = _configuration.GetValue("App:xch_address", "")!, + donation_address = _configuration.GetValue("App:donation_address", "")! + }; + } + + public async Task?> GetKeys(string storeId, CancellationToken cancellationToken) + { + try + { + var keys = await _memoryCache.GetOrCreateAsync($"{storeId}", async entry => + { + entry.SlidingExpiration = TimeSpan.FromMinutes(15); + _logger.LogInformation("Getting keys for {StoreId}", storeId); + return await _dataLayer.GetKeys(storeId, null, cancellationToken); + }); + + return keys; + } + catch + { + return null; // 404 in the api + } + } + + public async Task GetValue(string storeId, string key, CancellationToken cancellationToken) + { + try + { + var value = await _memoryCache.GetOrCreateAsync($"{storeId}-{key}", async entry => + { + entry.SlidingExpiration = TimeSpan.FromMinutes(15); + _logger.LogInformation("Getting value for {StoreId} {Key}", storeId, key); + return await _dataLayer.GetValue(storeId, key, null, cancellationToken); + }); + + return value; + } + catch + { + return null; // 404 in the api + } + } + + public async Task GetValueAsHtml(string storeId, CancellationToken cancellationToken) + { + var hexKey = HexUtils.ToHex("index.html"); + var value = await GetValue(storeId, hexKey, cancellationToken) ?? throw new InvalidOperationException("Couldn't retrieve expected key value"); + var decodedValue = HexUtils.FromHex(value); + var baseTag = $""; // Add the base tag + + return decodedValue.Replace("", $"\n {baseTag}"); + } + + public async Task GetValuesAsBytes(string storeId, dynamic json, CancellationToken cancellationToken) + { + var multipartFileNames = json.parts as IEnumerable ?? new List(); + var sortedFileNames = new List(multipartFileNames); + sortedFileNames.Sort((a, b) => + { + int numberA = int.Parse(a.Split(".part")[1]); + int numberB = int.Parse(b.Split(".part")[1]); + return numberA.CompareTo(numberB); + }); + + var hexPartsPromises = multipartFileNames.Select(async fileName => + { + var hexKey = HexUtils.ToHex(fileName); + return await GetValue(storeId, hexKey, cancellationToken); + }); + var dataLayerResponses = await Task.WhenAll(hexPartsPromises); + var resultHex = string.Join("", dataLayerResponses); + + return Convert.FromHexString(resultHex); + } +} diff --git a/Web2Gateway/HexUtils.cs b/Web2Gateway/HexUtils.cs new file mode 100644 index 0000000..2407b05 --- /dev/null +++ b/Web2Gateway/HexUtils.cs @@ -0,0 +1,32 @@ +using System.Text; + +/// +/// Provides utility methods for converting strings to and from hexadecimal representation. +/// +internal static class HexUtils +{ + /// + /// Converts a string to its hexadecimal representation. + /// + /// The input string to convert. + /// The hexadecimal representation of the input string. + public static string ToHex(this string input) => BitConverter.ToString(Encoding.UTF8.GetBytes(input)).Replace("-", "").ToLowerInvariant(); + + /// + /// Converts a hexadecimal string to its corresponding string representation. + /// + /// The hexadecimal string to convert. + /// The string representation of the hexadecimal input. + public static string FromHex(this string hex) + { + if (hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + hex = hex[2..]; + } + + return Encoding.UTF8.GetString(Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray()); + } +} diff --git a/Web2Gateway/Program.cs b/Web2Gateway/Program.cs new file mode 100644 index 0000000..84e730d --- /dev/null +++ b/Web2Gateway/Program.cs @@ -0,0 +1,57 @@ +using Web2Gateway; +using chia.dotnet; + +// doing all of this in the mini-api expressjs-like approach +// instead of the IActionResult approach + +var builder = WebApplication.CreateBuilder(args); + +if (builder.Environment.IsProduction()) +{ + builder.Services.AddApplicationInsightsTelemetry(); +} + +// we can take the path to an appsettings.json file as an argument +// if not provided, the default appsettings.json will be used and settings +// will come from there or from environment variables +if (args.Any()) +{ + var configurationBinder = new ConfigurationBuilder() + .AddJsonFile(args.First()); + + var config = configurationBinder.Build(); + builder.Configuration.AddConfiguration(config); +} + +// Add services to the container. +builder.Logging.ClearProviders() + .AddConsole(); + +builder.Services.AddControllers(); + +builder.Services.AddSingleton() + .AddSingleton((provider) => new HttpRpcClient(provider.GetService()!.GetDataLayerEndpoint())) + .AddSingleton((provider) => new DataLayerProxy(provider.GetService()!, "g2to3")) + .AddSingleton() + .AddEndpointsApiExplorer() + .AddSwaggerGen() + .AddMemoryCache(); + +var logger = LoggerFactory.Create(config => +{ + config.AddConsole(); +}).CreateLogger("Program"); +var configuration = builder.Configuration; + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseSwagger() + .UseSwaggerUI(); + +// the service end points are defined in here +app.ConfigureApi(logger) + .UseCors(); + +app.MapControllers(); +app.Run(); diff --git a/Web2Gateway/Properties/launchSettings.json b/Web2Gateway/Properties/launchSettings.json new file mode 100644 index 0000000..535d9f0 --- /dev/null +++ b/Web2Gateway/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:10936", + "sslPort": 44376 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5029", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7030;http://localhost:5029", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Web2Gateway/Utils.cs b/Web2Gateway/Utils.cs new file mode 100644 index 0000000..54e57c2 --- /dev/null +++ b/Web2Gateway/Utils.cs @@ -0,0 +1,41 @@ +using System.Text.Json; + +namespace Web2Gateway; + +internal static class Utils +{ + public static bool IsBase64Image(string data) + { + return data.StartsWith("data:image", StringComparison.OrdinalIgnoreCase); + } + + public static string? GetMimeType(string ext) + { + if (MimeTypes.TryGetMimeType(ext, out var mimeType)) + { + return mimeType; + } + + return null; + } + public static bool TryParseJson(string strInput, out dynamic? v) + { + try + { + strInput = strInput.Trim(); + if (strInput.StartsWith('{') && strInput.EndsWith('}') || //For object + strInput.StartsWith('[') && strInput.EndsWith(']')) //For array + { + v = JsonSerializer.Deserialize(strInput) ?? throw new Exception("Couldn't deserialize JSON"); + + return true; + } + } + catch (Exception) + { + } + + v = null; + return false; + } +} diff --git a/Web2Gateway/Web2Gateway.csproj b/Web2Gateway/Web2Gateway.csproj new file mode 100644 index 0000000..f07921f --- /dev/null +++ b/Web2Gateway/Web2Gateway.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + c1733864-825d-4ac4-ada8-5c3d6c1f6962 + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/Web2Gateway/appsettings.Development.json b/Web2Gateway/appsettings.Development.json new file mode 100644 index 0000000..72e6235 --- /dev/null +++ b/Web2Gateway/appsettings.Development.json @@ -0,0 +1,23 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.EntityFrameworkCore": "Information" + }, + "Console": { + "IncludeScopes": false, + "LogLevel": { + "Microsoft.EntityFrameworkCore": "Information", + "Default": "Information" + } + } + }, + "App": { + "chia_config_path": "", + "donation_address": "xch1c0wn057qtqequw7vc7uxvk38qpqat3rf2478shq7dw9rhm5y859swljp5a", + "xch_address": "xch1c0wn057qtqequw7vc7uxvk38qpqat3rf2478shq7dw9rhm5y859swljp5a" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/Web2Gateway/appsettings.json b/Web2Gateway/appsettings.json new file mode 100644 index 0000000..c5be1ce --- /dev/null +++ b/Web2Gateway/appsettings.json @@ -0,0 +1,30 @@ +{ + "Kestrel": { + "EndPoints": { + "Https": { + "Url": "http://0.0.0.0:8080" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.EntityFrameworkCore": "Information" + }, + "Console": { + "IncludeScopes": false, + "LogLevel": { + "Microsoft.EntityFrameworkCore": "Information", + "Default": "Information" + } + } + }, + "App": { + "chia_config_path": "", + "donation_address": "xch1c0wn057qtqequw7vc7uxvk38qpqat3rf2478shq7dw9rhm5y859swljp5a", + "xch_address": "xch1c0wn057qtqequw7vc7uxvk38qpqat3rf2478shq7dw9rhm5y859swljp5a" + }, + "AllowedHosts": "*" +} \ No newline at end of file