From c704a2178733c88f5fb85df23916e1eff0b4a282 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Wed, 20 Jul 2022 17:35:38 +0700 Subject: [PATCH] Creating Currency API endpoint --- Bot/paket.references | 4 +- CurrencyAPI/Currency.fs | 118 +++++++++++++++++++++++++++++++++ CurrencyAPI/CurrencyAPI.fsproj | 19 ++++++ CurrencyAPI/paket.references | 5 ++ CurrencyAPI/web.config | 9 +++ DegenzGame.sln | 6 ++ paket.dependencies | 5 +- paket.lock | 48 +++++--------- 8 files changed, 181 insertions(+), 33 deletions(-) create mode 100644 CurrencyAPI/Currency.fs create mode 100644 CurrencyAPI/CurrencyAPI.fsproj create mode 100644 CurrencyAPI/paket.references create mode 100644 CurrencyAPI/web.config diff --git a/Bot/paket.references b/Bot/paket.references index 018add7..4b4a306 100644 --- a/Bot/paket.references +++ b/Bot/paket.references @@ -2,9 +2,11 @@ FSharp.Core DSharpPlus DSharpPlus.Interactivity DSharpPlus.SlashCommands +FSharp.Core dotenv.net Npgsql.FSharp mixpanel-csharp Solnet.Rpc FsToolkit.ErrorHandling -FSharp.Data \ No newline at end of file +FSharp.Data +Newtonsoft.Json \ No newline at end of file diff --git a/CurrencyAPI/Currency.fs b/CurrencyAPI/Currency.fs new file mode 100644 index 0000000..779ebf9 --- /dev/null +++ b/CurrencyAPI/Currency.fs @@ -0,0 +1,118 @@ +module CurrencyAPI.App + +open System +open System.IO +open Microsoft.AspNetCore.Builder +open Microsoft.AspNetCore.Hosting +open Microsoft.AspNetCore.Http +open Microsoft.Extensions.Hosting +open Microsoft.Extensions.Logging +open Microsoft.Extensions.DependencyInjection +open Giraffe +open dotenv.net +open Npgsql.FSharp + +let prodEnv = DotEnv.Read(DotEnvOptions(envFilePaths = [ "../.prod.env"], overwriteExistingVars = false)) + +let ( _ , connStr ) = prodEnv.TryGetValue("DATABASE_URL") +let ( _ , apiKey ) = prodEnv.TryGetValue("API_KEY") + +let validateApiKey (ctx : HttpContext) = + match ctx.TryGetRequestHeader "X-API-Key" with + | Some key -> apiKey.Equals key + | None -> false + +let accessDenied = setStatusCode 401 >=> text "Access Denied" +let requiresApiKey = authorizeRequest validateApiKey accessDenied + +let getCurrentBalance (discordId : string) = + task { + let! amounts = + connStr + |> Sql.connect + |> Sql.parameters [ "did" , Sql.string discordId ] + |> Sql.query """SELECT gbt FROM "user" WHERE discord_id = @did""" + |> Sql.executeAsync (fun r -> r.int "gbt") + match amounts with + | [] -> return Error "User not found" + | a::_ -> return Ok a + } + +let get (discordId : string) : HttpHandler = + fun (next : HttpFunc) (ctx : HttpContext) -> + task { + try + match! getCurrentBalance discordId with + | Ok amount -> return! json {| Amount = amount |} next ctx + | Error e -> return! RequestErrors.notFound (json {| Error = e |}) next ctx + with ex -> + return! ServerErrors.internalError (json {| Error = ex.Message |}) next ctx + } + +let modify sign (discordId : string) : HttpHandler = + fun (next : HttpFunc) (ctx : HttpContext) -> + task { + let! body = ctx.BindJsonAsync<{|Amount:int|}>() + match! getCurrentBalance discordId with + | Ok current -> + let amount = body.Amount * sign + if current + amount < 0 then + return! RequestErrors.badRequest (json {| Error = "Insufficient funds" |}) next ctx + else + try + let! _ = + connStr + |> Sql.connect + |> Sql.parameters [ "did" , Sql.string discordId ; "amount" , Sql.int amount ] + |> Sql.query """UPDATE "user" SET gbt = GREATEST(gbt + @amount, 0) WHERE discord_id = @did""" + |> Sql.executeNonQueryAsync + return! json {| NewBalance = current + amount |} next ctx + with ex -> return! RequestErrors.notFound (json {| Error = ex.Message |}) next ctx + | Error e -> return! RequestErrors.notFound (json {| Error = e |}) next ctx + } + +let webApp = + choose [ + GET >=> requiresApiKey >=> routef "/user/%s/balance" get + PATCH >=> requiresApiKey >=> routef "/user/%s/balance/withdraw" (modify -1) + PATCH >=> requiresApiKey >=> routef "/user/%s/balance/deposit" (modify +1) + RequestErrors.NOT_FOUND "Not Found" ] + +let errorHandler (ex : Exception) (logger : ILogger) = + logger.LogError(ex, "An unhandled exception has occurred while executing the request.") + clearResponse >=> setStatusCode 500 >=> text ex.Message + +let configureApp (app : IApplicationBuilder) = + let env = app.ApplicationServices.GetService() + if env.IsDevelopment() then + app.UseDeveloperExceptionPage() + else + app.UseGiraffeErrorHandler(errorHandler) + |> ignore + app.UseGiraffe(webApp) + +let configureServices (services : IServiceCollection) = + services.AddGiraffe() |> ignore + +let configureLogging (builder : ILoggingBuilder) = + builder.AddConsole() + .AddDebug() |> ignore + +[] +let main args = + let contentRoot = Directory.GetCurrentDirectory() + let webRoot = Path.Combine(contentRoot, "WebRoot") + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults( + fun webHostBuilder -> + webHostBuilder + .UseContentRoot(contentRoot) + .UseWebRoot(webRoot) + .ConfigureKestrel(fun opt -> opt.AddServerHeader <- false) + .Configure(Action configureApp) + .ConfigureServices(configureServices) + .ConfigureLogging(configureLogging) + |> ignore) + .Build() + .Run() + 0 \ No newline at end of file diff --git a/CurrencyAPI/CurrencyAPI.fsproj b/CurrencyAPI/CurrencyAPI.fsproj new file mode 100644 index 0000000..bbd3e99 --- /dev/null +++ b/CurrencyAPI/CurrencyAPI.fsproj @@ -0,0 +1,19 @@ + + + + net6.0 + CurrencyAPI.App + false + true + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/CurrencyAPI/paket.references b/CurrencyAPI/paket.references new file mode 100644 index 0000000..054c675 --- /dev/null +++ b/CurrencyAPI/paket.references @@ -0,0 +1,5 @@ +FSharp.Data +FSharp.Core +dotenv.net +Npgsql.FSharp +Giraffe diff --git a/CurrencyAPI/web.config b/CurrencyAPI/web.config new file mode 100644 index 0000000..f81ac42 --- /dev/null +++ b/CurrencyAPI/web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/DegenzGame.sln b/DegenzGame.sln index 4f1c150..115934b 100644 --- a/DegenzGame.sln +++ b/DegenzGame.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.30114.105 MinimumVisualStudioVersion = 10.0.40219.1 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Bot", "Bot\Bot.fsproj", "{FF9E58A6-1A1D-4DEC-B52D-265F215BF315}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CurrencyAPI", "CurrencyAPI\CurrencyAPI.fsproj", "{AFA1A9F3-625E-44CA-83DA-A50756D119B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,5 +24,9 @@ Global {44151B85-0D17-4270-AD72-490A6D8D6290}.Debug|Any CPU.Build.0 = Debug|Any CPU {44151B85-0D17-4270-AD72-490A6D8D6290}.Release|Any CPU.ActiveCfg = Release|Any CPU {44151B85-0D17-4270-AD72-490A6D8D6290}.Release|Any CPU.Build.0 = Release|Any CPU + {AFA1A9F3-625E-44CA-83DA-A50756D119B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFA1A9F3-625E-44CA-83DA-A50756D119B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFA1A9F3-625E-44CA-83DA-A50756D119B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFA1A9F3-625E-44CA-83DA-A50756D119B5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/paket.dependencies b/paket.dependencies index 8c05677..22a0f65 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -11,11 +11,12 @@ nuget DSharpPlus.SlashCommands >= 4.3.0-nightly-01135 nuget FSharp.Data nuget FsToolkit.ErrorHandling -nuget MongoDB.Driver nuget dotenv.net 3.1.1 nuget Npgsql.FSharp nuget mixpanel-csharp 5.0.0 nuget Solnet.Extensions nuget Solnet.KeyStore nuget Solnet.Programs -nuget Solnet.Rpc \ No newline at end of file +nuget Solnet.Rpc + +nuget Giraffe diff --git a/paket.lock b/paket.lock index 15b27e4..7ca9e0d 100644 --- a/paket.lock +++ b/paket.lock @@ -4,9 +4,6 @@ NUGET remote: https://api.nuget.org/v3/index.json Chaos.NaCl.Standard (1.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) ConcurrentHashSet (1.3) - DnsClient (1.6) - Microsoft.Win32.Registry (>= 5.0) - System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net471)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net471)) (&& (== netstandard2.1) (< netstandard2.0)) dotenv.net (3.1.1) System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (< netstandard2.0)) DSharpPlus (4.3.0-nightly-01135) @@ -35,6 +32,15 @@ NUGET FSharp.Core (>= 4.7.2) FsToolkit.ErrorHandling (2.13) FSharp.Core (>= 4.7.2) + Giraffe (6.0) + FSharp.Core (>= 6.0.1) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Giraffe.ViewEngine (>= 1.3) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Microsoft.IO.RecyclableMemoryStream (>= 2.2) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Newtonsoft.Json (>= 13.0.1) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + System.Text.Json (>= 6.0.2) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Utf8Json (>= 1.3.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Giraffe.ViewEngine (1.4) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + FSharp.Core (>= 5.0) Microsoft.Bcl.AsyncInterfaces (6.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461)) Microsoft.Bcl.HashCode (1.1.1) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) @@ -89,31 +95,14 @@ NUGET Microsoft.Extensions.Primitives (>= 6.0) Microsoft.Extensions.Primitives (6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) System.Runtime.CompilerServices.Unsafe (>= 6.0) + Microsoft.IO.RecyclableMemoryStream (2.2) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Microsoft.NETCore.Platforms (6.0.2) Microsoft.NETCore.Targets (5.0) Microsoft.Win32.Primitives (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) - Microsoft.Win32.Registry (5.0) - System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= monoandroid) (< netstandard1.3)) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0) (== netstandard2.1) - System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0) (== netstandard2.1) - System.Security.AccessControl (>= 5.0) - System.Security.Principal.Windows (>= 5.0) mixpanel-csharp (5.0) - MongoDB.Bson (2.15) - System.Runtime.CompilerServices.Unsafe (>= 5.0) - MongoDB.Driver (2.15) - MongoDB.Bson (>= 2.15) - MongoDB.Driver.Core (>= 2.15) - MongoDB.Libmongocrypt (>= 1.3) - MongoDB.Driver.Core (2.15) - DnsClient (>= 1.6) - MongoDB.Bson (>= 2.15) - MongoDB.Libmongocrypt (>= 1.3) - SharpCompress (>= 0.30.1) - System.Buffers (>= 4.5.1) - MongoDB.Libmongocrypt (1.3) Newtonsoft.Json (13.0.1) Npgsql (6.0.3) Microsoft.Bcl.AsyncInterfaces (>= 6.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) @@ -177,9 +166,6 @@ NUGET runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) runtime.ubuntu.18.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - SharpCompress (0.30.1) - System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461)) - System.Text.Encoding.CodePages (>= 5.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (== netstandard2.1) Solnet.Extensions (6.0.11) Solnet.Programs (>= 6.0.11) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Solnet.Rpc (>= 6.0.11) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) @@ -197,7 +183,7 @@ NUGET Solnet.Wallet (6.0.11) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Chaos.NaCl.Standard (>= 1.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Portable.BouncyCastle (>= 1.9) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) - System.Buffers (4.5.1) + System.Buffers (4.5.1) - restriction: || (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.0) (< netstandard2.1)) (&& (== net6.0) (< netstandard1.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (< netstandard2.1) (>= xamarinios)) (&& (== net6.0) (< netstandard2.1) (>= xamarinmac)) (&& (== net6.0) (< netstandard2.1) (>= xamarintvos)) (&& (== net6.0) (< netstandard2.1) (>= xamarinwatchos)) (== netstandard2.0) (== netstandard2.1) System.Collections (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -397,6 +383,8 @@ NUGET System.IO (>= 4.3) System.Reflection.Primitives (>= 4.3) System.Runtime (>= 4.3) + System.Reflection.Emit (4.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + System.Reflection.Emit.Lightweight (4.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) System.Reflection.Extensions (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -444,8 +432,6 @@ NUGET System.Resources.ResourceManager (>= 4.3) System.Runtime (>= 4.3) System.Runtime.Extensions (>= 4.3) - System.Security.AccessControl (6.0) - System.Security.Principal.Windows (>= 5.0) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0) (== netstandard2.1) System.Security.Claims (4.3) System.Collections (>= 4.3) System.Globalization (>= 4.3) @@ -541,9 +527,6 @@ NUGET Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) - System.Text.Encoding.CodePages (6.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0) (== netstandard2.1) - System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0) (== netstandard2.1) - System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Text.Encoding.Extensions (4.3) Microsoft.NETCore.Platforms (>= 1.1) Microsoft.NETCore.Targets (>= 1.1) @@ -573,3 +556,8 @@ NUGET Microsoft.NETCore.Targets (>= 1.1) System.Runtime (>= 4.3) System.ValueTuple (4.5) + Utf8Json (1.3.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + System.Reflection.Emit (>= 4.3) + System.Reflection.Emit.Lightweight (>= 4.3) + System.Threading.Tasks.Extensions (>= 4.4) + System.ValueTuple (>= 4.4)