diff --git a/.env.dist b/.env.dist index 853f0d8..937af9b 100644 --- a/.env.dist +++ b/.env.dist @@ -1,5 +1,8 @@ # PGSQL DATABASE_URL=postgres://user:password@host/db_name +DATABASE_RUN_MIGRATIONS_ON_IGNITE=true +DB_PASSWORD=db_password +DB_USER=db_user # LND SERVER CONFIGURATION LND_ADDRESS="https://umbrel.local:10009" @@ -13,4 +16,10 @@ DEFAULT_INVOICE_EXPIRY=3000 # JWT CONFIGURATION JWT_TOKEN_DURATION=1000 -JWT_TOKEN_SECRET=secret \ No newline at end of file +JWT_TOKEN_SECRET=secret + +#CORS POLICY +CORS_ORIGIN_POLICY="*" +CORS_METHOD_POLICY="POST, GET, PATCH, OPTIONS" +CORS_HEADERS_POLICY="*" +CORS_CREDENTIALS_POLICY="true" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5871fc9..d17408f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ _dump target .env src/lnd/config/ -rocket.toml \ No newline at end of file +rocket.toml +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 05ca7ca..951022e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher 0.3.0", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -11,11 +46,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" -version = "1.0.44" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "ascii" @@ -25,9 +69,9 @@ checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -35,9 +79,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -46,9 +90,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -57,9 +101,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" dependencies = [ "autocfg", ] @@ -77,15 +121,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "base-x" -version = "0.2.8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -95,13 +133,13 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bcrypt" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe4fef31efb0f76133ae8e3576a88e58edb7cfc5584c81c758c349ba46b43fc" +checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641" dependencies = [ "base64", "blowfish", - "getrandom 0.2.3", + "getrandom 0.2.6", "zeroize", ] @@ -140,6 +178,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "blowfish" version = "0.9.1" @@ -147,14 +194,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", - "cipher", + "cipher 0.4.3", ] [[package]] name = "bson" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903a4f4c7aa97921f1703acac1fd524e9e082b3228edd34dde07758c0c92c672" +checksum = "de0aa578035b938855a710ba58d43cfb4d435f3619f99236fb35922a574d6cb1" dependencies = [ "base64", "chrono", @@ -164,14 +211,14 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "uuid", + "uuid 0.8.2", ] [[package]] name = "bumpalo" -version = "3.7.1" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byteorder" @@ -187,9 +234,20 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.70" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfb" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "466bc70ba95e7ad3322ce4c69a2bc22635a0f9535c1ffb7174bc28c286764517" +dependencies = [ + "byteorder", + "fnv", + "uuid 1.1.0", +] [[package]] name = "cfg-if" @@ -207,10 +265,19 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time 0.1.43", + "time 0.1.44", "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "cipher" version = "0.4.3" @@ -234,12 +301,6 @@ dependencies = [ "unreachable", ] -[[package]] -name = "const_fn" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" - [[package]] name = "convert_case" version = "0.4.0" @@ -248,15 +309,31 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" dependencies = [ + "aes-gcm", + "base64", + "hkdf", + "hmac", "percent-encoding", - "time 0.2.27", + "rand 0.8.5", + "sha2", + "subtle", + "time 0.3.9", "version_check", ] +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -267,16 +344,25 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "derive_more" -version = "0.99.16" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.3.3", + "rustc_version", "syn", ] @@ -336,16 +422,16 @@ dependencies = [ "diesel_derives", "pq-sys", "r2d2", - "uuid", + "uuid 0.8.2", ] [[package]] name = "diesel-derive-enum" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70806b70be328e646f243680a3fc93b3cfdd6db373faa5110660a5dd5af243bc" +checksum = "6c8910921b014e2af16298f006de12aa08af894b71f0f49a486ab6d74b17bbed" dependencies = [ - "heck", + "heck 0.4.0", "proc-macro2", "quote", "syn", @@ -373,10 +459,15 @@ dependencies = [ ] [[package]] -name = "discard" -version = "1.0.4" +name = "digest" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] [[package]] name = "dotenv" @@ -392,13 +483,22 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding_rs" -version = "0.8.28" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if", ] +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "figment" version = "0.10.6" @@ -437,9 +537,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -452,9 +552,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -462,9 +562,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-enum" @@ -479,9 +579,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -490,18 +590,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "autocfg", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -509,23 +607,22 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ - "autocfg", "futures-channel", "futures-core", "futures-io", @@ -535,8 +632,6 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -576,13 +671,23 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -603,9 +708,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -616,7 +721,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.2", "tracing", ] @@ -635,6 +740,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -650,15 +761,33 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", - "itoa 0.4.8", + "itoa", ] [[package]] @@ -674,21 +803,21 @@ dependencies = [ [[package]] name = "httparse" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ "bytes", "futures-channel", @@ -699,7 +828,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.1", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -733,20 +862,29 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", "hashbrown", "serde", ] +[[package]] +name = "infer" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55" +dependencies = [ + "cfb", +] + [[package]] name = "inlinable_string" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] name = "inout" @@ -759,9 +897,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -797,30 +935,24 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] [[package]] name = "jsonwebtoken" -version = "8.0.1" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "012bb02250fdd38faa5feee63235f7a459974440b9b57593822414c31f92839e" +checksum = "cc9051c17f81bae79440afa041b3a278e1de71bfb96d32454b477fd4703ccb6f" dependencies = [ "base64", "pem", @@ -832,9 +964,9 @@ dependencies = [ [[package]] name = "juniper" -version = "0.15.7" +version = "0.15.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637ffa8a8d8a05aed3331449e311f145864adcd82442d82e54d0522decb7cecf" +checksum = "21ac55c9084d08a7e315d78e2b15b7cc220f5eb67413c6ebf6967ee5de3b69fc" dependencies = [ "async-trait", "bson", @@ -849,14 +981,14 @@ dependencies = [ "smartstring", "static_assertions", "url", - "uuid", + "uuid 0.8.2", ] [[package]] name = "juniper_codegen" -version = "0.15.7" +version = "0.15.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a040e09482a45e77dd2dafa0d9d2651d17faf0ac674da0c93eabc3075ee24997" +checksum = "aee97671061ad50301ba077d054d295e01d31a1868fbd07902db651f987e71db" dependencies = [ "proc-macro-error", "proc-macro2", @@ -866,9 +998,9 @@ dependencies = [ [[package]] name = "juniper_rocket" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24868ce3d00537b353d2e18882133a317f504782730bb2979df00c1bfcc575c" +checksum = "a431e6f03bc31bd74498a837e87ddf635deef3c1a2026e59680dec2552c84c28" dependencies = [ "futures", "juniper", @@ -876,6 +1008,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "juniper_rocket_multipart_handler" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4028f0919613020b6308b9c161ac4c93ae70b9581f2d7aa49ada9d37b59763af" +dependencies = [ + "juniper", + "juniper_rocket", + "multer", + "rocket", + "serde", + "serde_json", + "tokio-util 0.7.2", + "uuid 1.1.0", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -884,9 +1032,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "lightning" @@ -930,51 +1078,68 @@ dependencies = [ "dotenv", "futures", "hex", + "infer", "itconfig", "jsonwebtoken", "juniper", "juniper_rocket", + "juniper_rocket_multipart_handler", "lazy_static", "lightning-invoice", + "multer", "regex", "rocket", + "rocket-multipart-form-data", "rocket_sync_db_pools", "serde", "serde_json", + "tokio-util 0.7.2", "tonic", "tonic_lnd", - "uuid", + "uuid 0.8.2", ] [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "loom" -version = "0.5.1" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2111607c723d7857e0d8299d5ce7a0bf4b844d3e44f8de136b13da513eaf8fc4" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", "generator", "scoped-tls", "serde", "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", ] [[package]] @@ -985,9 +1150,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "migrations_internals" @@ -1018,31 +1183,21 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mio" -version = "0.7.13" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] name = "multer" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" +checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" dependencies = [ "bytes", "encoding_rs", @@ -1050,11 +1205,11 @@ dependencies = [ "http", "httparse", "log", + "memchr", "mime", - "spin 0.9.2", + "spin 0.9.3", "tokio", - "tokio-util", - "twoway", + "tokio-util 0.6.10", "version_check", ] @@ -1064,15 +1219,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1086,9 +1232,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1096,18 +1242,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -1115,18 +1261,24 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "once_cell" -version = "1.8.0" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" + +[[package]] +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parking_lot" @@ -1136,7 +1288,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.3", ] [[package]] @@ -1153,6 +1315,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "pear" version = "0.2.3" @@ -1191,15 +1366,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - [[package]] name = "petgraph" version = "0.5.1" @@ -1212,18 +1378,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -1232,9 +1398,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1242,11 +1408,23 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pq-sys" @@ -1281,25 +1459,13 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1342,7 +1508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603" dependencies = [ "bytes", - "heck", + "heck 0.3.3", "itertools", "log", "multimap", @@ -1395,14 +1561,14 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -1414,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" dependencies = [ "log", - "parking_lot", + "parking_lot 0.11.2", "scheduled-thread-pool", ] @@ -1428,19 +1594,18 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -1478,7 +1643,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.6", ] [[package]] @@ -1490,38 +1655,29 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "685d58625b6c2b83e4cc88a27c4bf65adb7b6b16dbdc413e515c9405b47432ab" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f" dependencies = [ "proc-macro2", "quote", @@ -1539,6 +1695,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.25" @@ -1571,9 +1736,9 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" dependencies = [ "async-stream", "async-trait", @@ -1589,29 +1754,41 @@ dependencies = [ "memchr", "multer", "num_cpus", - "parking_lot", + "parking_lot 0.12.0", "pin-project-lite", - "rand 0.8.4", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", "serde", "state", "tempfile", - "time 0.2.27", + "time 0.3.9", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.2", "ubyte", "version_check", "yansi", ] +[[package]] +name = "rocket-multipart-form-data" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4b5ad1a62bfa8a250204c41cf02c94cf9ef135a680669cdb39dbbeb8b0132e" +dependencies = [ + "mime", + "multer", + "rocket", + "tokio-util 0.7.2", +] + [[package]] name = "rocket_codegen" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" dependencies = [ "devise", "glob", @@ -1625,37 +1802,39 @@ dependencies = [ [[package]] name = "rocket_http" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" dependencies = [ "cookie", "either", + "futures", "http", "hyper", "indexmap", "log", "memchr", - "mime", - "parking_lot", "pear", "percent-encoding", "pin-project-lite", "ref-cast", + "rustls 0.20.6", + "rustls-pemfile 1.0.0", "serde", "smallvec", "stable-pattern", "state", - "time 0.2.27", + "time 0.3.9", "tokio", + "tokio-rustls 0.23.4", "uncased", ] [[package]] name = "rocket_sync_db_pools" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cfdfebd552d075c368e641c88a5cd6ce1c58c5c710548aeb777abb48830f4b" +checksum = "5fa48b6ab25013e9812f1b0c592741900b3a2a83c0936292e0565c0ac842f558" dependencies = [ "diesel", "r2d2", @@ -1667,9 +1846,9 @@ dependencies = [ [[package]] name = "rocket_sync_db_pools_codegen" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267808c094db5366e1d8925aaf9f2ce05ff9b3bd92cb18c7040a1fe219c2e25" +checksum = "280ef2d232923e69cb93da156972eb5476a7cce5ba44843f6608f46a4abf7aab" dependencies = [ "devise", "quote", @@ -1677,33 +1856,36 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] -name = "rustc_version" -version = "0.3.3" +name = "rustls" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "semver 0.11.0", + "base64", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ - "base64", "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -1715,17 +1897,26 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + [[package]] name = "rustversion" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "scheduled-thread-pool" @@ -1733,7 +1924,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7" dependencies = [ - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -1758,6 +1949,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.20.3" @@ -1769,60 +1970,33 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" dependencies = [ "cc", ] [[package]] name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser 0.7.0", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser 0.10.2", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "semver-parser" -version = "0.10.2" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -1831,21 +2005,35 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "indexmap", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] [[package]] -name = "sha1" -version = "0.6.0" +name = "sha2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] [[package]] name = "signal-hook-registry" @@ -1870,30 +2058,30 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smartstring" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa6a31c0c2b21327ce875f7e8952322acfcfd0c27569a6e18a647281352c9b" +checksum = "e714dff2b33f2321fdcd475b71cec79781a692d846f37f415fb395a1d2bcd48e" dependencies = [ "static_assertions", ] [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -1907,9 +2095,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" [[package]] name = "stable-pattern" @@ -1920,20 +2108,11 @@ dependencies = [ "memchr", ] -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - [[package]] name = "state" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" dependencies = [ "loom", ] @@ -1945,74 +2124,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version 0.2.3", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" +name = "subtle" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.77" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi", @@ -2020,18 +2156,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2039,27 +2175,22 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.1.43" +name = "thread_local" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "libc", - "winapi", + "once_cell", ] [[package]] name = "time" -version = "0.2.27" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ - "const_fn", "libc", - "standback", - "stdweb", - "time-macros 0.1.1", - "version_check", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] @@ -2069,21 +2200,11 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ - "itoa 1.0.1", + "itoa", "libc", "num_threads", "quickcheck", - "time-macros 0.2.4", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", + "time-macros", ] [[package]] @@ -2092,24 +2213,11 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - [[package]] name = "tinyvec" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2122,11 +2230,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.12.0" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -2135,6 +2242,7 @@ dependencies = [ "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "winapi", ] @@ -2151,9 +2259,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -2166,16 +2274,27 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.6", "tokio", - "webpki", + "webpki 0.22.0", ] [[package]] name = "tokio-stream" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", @@ -2184,9 +2303,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", @@ -2196,11 +2315,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -2227,9 +2360,9 @@ dependencies = [ "prost 0.9.0", "prost-derive 0.9.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tokio-stream", - "tokio-util", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2257,29 +2390,29 @@ checksum = "e133e6e4c0162f45dea89ba7635abee93632ca7d08bdba29eefff2dc5e00aed7" dependencies = [ "hex", "prost 0.9.0", - "rustls", - "rustls-pemfile", + "rustls 0.19.1", + "rustls-pemfile 0.2.1", "tokio", "tonic", "tonic-build", - "webpki", + "webpki 0.21.4", ] [[package]] name = "tower" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60422bc7fefa2f3ec70359b8ff1caff59d785877eb70595904605bcc412470f" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", "indexmap", "pin-project", - "rand 0.8.4", + "pin-project-lite", + "rand 0.8.5", "slab", "tokio", - "tokio-stream", - "tokio-util", + "tokio-util 0.7.2", "tower-layer", "tower-service", "tracing", @@ -2299,9 +2432,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.28" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if", "log", @@ -2312,9 +2445,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -2323,11 +2456,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -2341,21 +2475,40 @@ dependencies = [ ] [[package]] -name = "try-lock" -version = "0.2.3" +name = "tracing-log" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] [[package]] -name = "twoway" -version = "0.2.2" +name = "tracing-subscriber" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ - "memchr", - "unchecked-index", + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -2364,40 +2517,34 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ubyte" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +checksum = "a58e29f263341a29bb79e14ad7fda5f63b1c7e48929bad4c685d7876b1d04e94" dependencies = [ "serde", ] -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - [[package]] name = "uncased" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "serde", "version_check", ] [[package]] -name = "unchecked-index" -version = "0.2.2" +name = "unicode-bidi" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] -name = "unicode-bidi" -version = "0.3.6" +name = "unicode-ident" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" @@ -2410,15 +2557,25 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] [[package]] name = "unreachable" @@ -2453,10 +2610,26 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.6", + "serde", +] + +[[package]] +name = "uuid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93bbc61e655a4833cf400d0d15bf3649313422fa7572886ad6dab16d79886365" +dependencies = [ + "getrandom 0.2.6", "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2465,9 +2638,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -2493,15 +2666,21 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2509,9 +2688,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -2524,9 +2703,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2534,9 +2713,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -2547,15 +2726,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -2571,11 +2750,21 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "which" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -2604,14 +2793,57 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "yansi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zeroize" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" +checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" diff --git a/Cargo.toml b/Cargo.toml index a37b433..2bb6b63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ edition = "2018" [dependencies] futures = "0.3.1" -rocket = "0.5.0-rc.1" -juniper = "0.15.7" -juniper_rocket = "0.8.0" +rocket = { version = "0.5.0-rc.2", features = ["tls"] } +juniper = "0.15.9" +juniper_rocket = "0.8.2" diesel = { version = "1.4.7", features = ["postgres","r2d2","chrono","uuidv07"] } dotenv = "0.15.0" chrono = { version = "0.4.19", features = ["serde"] } @@ -24,11 +24,20 @@ serde = "1.0.136" serde_json = "1.0.68" lightning-invoice = "0.14.0" diesel_migrations = "1.4.0" +rocket-multipart-form-data = "0.10.3" uuid = { version = "0.8.2", features = ["serde", "v4"] } +multer = "2.0.2" jsonwebtoken = "8.0.1" regex = "1.5.5" -bcrypt = "0.12" +bcrypt = "0.13.0" +juniper_rocket_multipart_handler = "0.1.0" +infer = "0.8.1" + +[dependencies.tokio-util] +version = "0.7.1" + [dependencies.rocket_sync_db_pools] -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" + +features = ["diesel_postgres_pool"] -features = ["diesel_postgres_pool"] \ No newline at end of file diff --git a/README.md b/README.md index de7afd4..73168bc 100644 --- a/README.md +++ b/README.md @@ -2,316 +2,23 @@ ![graphQLN](https://github.com/Asone/graphQLN/actions/workflows/rust.yml/badge.svg) -This project is a demo of a [graphQL](https://graphql.org/) API with a built-in bitcoin [lightning network](https://en.wikipedia.org/wiki/Lightning_Network) paywall mechanism, built with [Rustlang](https://www.rust-lang.org/). +GraphQLN is a proof-of-concept of a [graphQL](https://graphql.org/) API with a built-in bitcoin [lightning network](https://en.wikipedia.org/wiki/Lightning_Network) paywall mechanism, built with [Rustlang](https://www.rust-lang.org/). +## Status -## Install project - -1. Clone the project : - -``` -git clone -``` - -2. go to the folder and install dependencies : -``` -cargo install --path . -``` - -## Set-up the project - -In order to be able to launch the project you need to set-up a few configurations. Once all set-up, you shall be able to launch the server with - -``` -cargo run -``` - -You'll find then a [graphiQL](https://github.com/graphql/graphiql) interface on [http://localhost:8000](http://localhost:8000) -### Configure Database connection - -The current project uses [postgres]() as database engine. -To set-up the connection copy the `rocket.toml.dist` file as `rocket.toml` and fill the connection URL as mentioned. - -``` -main_db = { url = "postgres://:@/"} -``` - -### Configure diesel options - -You need to create a `diesel.toml` file in the root folder to specify to diesel its configuration. You can use the `diesel.toml.dist` as a simple example of the configuration file. - -### Configure LND connection - -The current project uses LND server to handle Lightning network. - -In a `.env` file in the root folder of the project, You'll need to provide : - -- `LND_ADDRESS` : The address to reach the LND server -- `LND_CERTFILE_PATH` : the ssl certification file of your LND server -- `LND_MACAROON_PATH` : The macaroon that will allow the rocket server to connect to your LND server. - -**Note that the current project requires a macaroon with at least invoice write/read access.** - -You can use the `.env.dist` file as a template for that. - -``` -LND_ADDRESS="https://umbrel.local:10009" -LND_CERTFILE_PATH="path/to/the/lnd.cert" -LND_MACAROON_PATH="path/to/the/invoice.macaroon" -``` - -## Available requests - -### Mutation - -There is currently a single mutation available through the API, that allows you to create a post. Note that there is no restriction (like user guard) to create posts. - -Request : - -````graphql -mutation { - createPost(post: { - title: "Ad lorem ipsum", - excerpt: "This is a short description of the post", - content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed laoreet suscipit ullamcorper. Etiam sit amet justo dapibus, elementum magna sit amet, faucibus risus. Nullam at augue in quam tristique posuere. Nullam congue dignissim odio non sagittis. Sed in libero erat. Maecenas dictum blandit purus. Suspendisse eget sem suscipit, auctor risus in, ornare orci. Curabitur id facilisis nisl, vitae interdum libero. Aenean commodo nulla sit amet arcu consectetur, non tristique purus elementum. Sed ex sem, blandit eleifend fringilla ac, sagittis auctor ipsum.", - published: true, - price: 100 - }){ - title - excerpt - price - } -} -```` - -will return: - -````json -{ - "data": { - "createPost": { - "title": "Ad lorem ipsum", - "excerpt": "This is a short description of the post", - "price": 100 - } - } -} -```` -### Queries - -#### **Get posts list** - -Request : -```graphql -{ - getPostsList{ - uuid - title - excerpt - price - } -} -``` - -will return something like : - -```json -{ - "data": { - "getPostsList": [ - { - "uuid": "9f3711b4-f733-4911-9863-0c4ee575ca10", - "title": "ad lorem ipsum", - "excerpt": "alea jacta est", - "price": 100 - }, - { - "uuid": "e07677d1-4a45-422e-ac9b-a3a39cd91f0c", - "title": "Ad lorem ipsum", - "excerpt": "This is a short description of the post", - "price": 100 - } - ] - } -} -``` -#### **Get a single post** - -This is the query where the paywall and most of the LN Network interaction is applied. - -You'll find the code block that handles the paywall [here](https://github.com/Asone/graphqln/blob/master/src/graphql/query.rs#L40) - -The request takes an object with two fields : -- The post uuid -- The payment request that should allow the server to identify the access to the content has been paid. This field is optional, and if not provided, the api will respond with an error providing an invoice. - - -For example, providing the request like this : - -```graphql -{ - getPost(post:{ - uuid: "9f3711b4-f733-4911-9863-0c4ee575ca10" - }){ - uuid - title - excerpt - content - price - } -} -``` - -You'll get a response similar to this : - -```json -{ - "data": null, - "errors": [ - { - "message": "Payable post. Payment not found.. Use provided payment request.", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "getPost" - ], - "extensions": { - "payment_request": "lnbc1u1pshhszcpp5e3wpuwldl92zumajqs58k69stru6g9rc43nw0v7uy5rnk8vl7f0sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5ggd3dps9r27dcmxtmj463uct653n2agqttmjhm3qw6wgkfzaqw9s9qyyssq8zga2evqh8lt7kv40269puz3xehezxqvauhz4he0zvyke0x642q33jy85za4qtwa5p24x0vh5ve2p5ztqw64mlpsuwj5ml3ke8rl67spzzhwhv", - "r_hash": "cc5c1e3bedf9542e6fb204287b68b058f9a41478ac66e7b3dc25073b1d9ff25f" - } - } - ] -} -``` - -Note the [extensions](https://github.com/graphql/graphql-spec/blame/main/spec/Section%207%20--%20Response.md#L201-L204) sub-object which are part of the graphQL spec. -It provides a `payment_request` which will allow user to pay to access the content. - -The user can try to refetch the content providing the same `payment_request` within the request. - -```graphql -{ - getPost(post:{ - uuid: "9f3711b4-f733-4911-9863-0c4ee575ca10", - paymentRequest: "lnbc1u1pshhszcpp5e3wpuwldl92zumajqs58k69stru6g9rc43nw0v7uy5rnk8vl7f0sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5ggd3dps9r27dcmxtmj463uct653n2agqttmjhm3qw6wgkfzaqw9s9qyyssq8zga2evqh8lt7kv40269puz3xehezxqvauhz4he0zvyke0x642q33jy85za4qtwa5p24x0vh5ve2p5ztqw64mlpsuwj5ml3ke8rl67spzzhwhv" - }){ - uuid - title - excerpt - content - price - } -} -``` - -When provided, the server will check the invoice state for the provided `payment_request` and its local association with the requested post to ensure the user can access the content. - -A few cases can happen. - -If user requests with another payment request that is not the one provided, it will get a similar response to this : - -```json -{ - "data": null, - "errors": [ - { - "message": "No recorded payment request related to the requested post found with the payment requested provided. Proceed to payment with the provided request payment", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "getPost" - ], - "extensions": { - "payment_request": "lnbc1u1pshh3vypp5zyzf88glqv7gkuvqn95j97nzlt32xk8c9tu8t8dzywyax0vdw72sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5275n2cjqjgly9trkmucfux3krxw9z5na7wjmtklvua0j8tsw0pts9qyyssqw5kcm6r0zp4d5uu0p9zq5ehx9zen4svt63tvj20pa5kwfevv3p7x863f7mz4spa4w6p326jkegjq3gwtf8jzzr72nukyn8aw2s2gayqppmrhmq", - "r_hash": "1104939d1f033c8b7180996922fa62fae2a358f82af8759da22389d33d8d7795" - } - } - ] -} -``` - -Note how the server provides automatically a new invoice so the front-end can provide it to the user. - -If user requests the data without paying but providing the payment request he will get something similar to this : - -```json -{ - "data": null, - "errors": [ - { - "message": "Awaiting for payment to be done.", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "getPost" - ] - } - ] -} -``` - -If invoice expired, user will get something similar to this : - - -```json -{ - "data": null, - "errors": [ - { - "message": "Payment expired or canceled. Proceed to payment with the provided request payment", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "getPost" - ], - "extensions": { - "payment_request": "lnbc1u1pshh3vypp5zyzf88glqv7gkuvqn95j97nzlt32xk8c9tu8t8dzywyax0vdw72sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5275n2cjqjgly9trkmucfux3krxw9z5na7wjmtklvua0j8tsw0pts9qyyssqw5kcm6r0zp4d5uu0p9zq5ehx9zen4svt63tvj20pa5kwfevv3p7x863f7mz4spa4w6p326jkegjq3gwtf8jzzr72nukyn8aw2s2gayqppmrhmq", - "r_hash": "1104939d1f033c8b7180996922fa62fae2a358f82af8759da22389d33d8d7795" - } - } - ] -} -``` - -Note how the server regenerates automatically a new invoice to be provided to the user. - -Finally, once the user has paid the invoice, he will get the content with a response similar to this : - -```json -{ - "data": { - "getPost": { - "uuid": "9f3711b4-f733-4911-9863-0c4ee575ca10", - "title": "ad lorem ipsum", - "excerpt": "alea jacta est", - "content": "ad lorem ipsum dolor sit amet fluctuat nec mergitur rosa rosae rosam", - "price": 100 - } - } -} -``` - -Note that the user can reuse the payment request as many times as he wants as -we do store the association invoice - content and server will check to the LND instance that the invoice is settled. +The project is still under development and lacks tests. +## Features +- User authentication protected mutations +- API paywall over Lightning +- Data query paywall over lightning +## Documentation +An extended documentation is provided in the `docs` folder to help you understand how to install, configure and run the server : +- [Installation](./docs/installation.md) +- [Configuration](./docs/configuration.md) +- [Paywall](./docs/paywall.md) ## Main dependencies The project reliess on many dependencies to build and distribute the API. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..c648e95 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,41 @@ +## Set-up the project + +In order to be able to launch the project you need to set-up a few configurations. Once all set-up, you shall be able to launch the server with + +``` +cargo run +``` + +You'll find then a [graphiQL](https://github.com/graphql/graphiql) interface on [http://localhost:8000](http://localhost:8000) +### Configure Database connection + +The current project uses [postgres]() as database engine. +To set-up the connection copy the `rocket.toml.dist` file as `rocket.toml` and fill the connection URL as mentioned. + +``` +main_db = { url = "postgres://:@/"} +``` + +### Configure diesel options + +You need to create a `diesel.toml` file in the root folder to specify to diesel its configuration. You can use the `diesel.toml.dist` as a simple example of the configuration file. + +### Configure LND connection + +The current project uses LND server to handle Lightning network. + +In a `.env` file in the root folder of the project, You'll need to provide : + +- `LND_ADDRESS` : The address to reach the LND server +- `LND_CERTFILE_PATH` : the ssl certification file of your LND server +- `LND_MACAROON_PATH` : The macaroon that will allow the rocket server to connect to your LND server. + +**Note that the current project requires a macaroon with at least invoice write/read access.** + +You can use the `.env.dist` file as a template for that. + +``` +LND_ADDRESS="https://umbrel.local:10009" +LND_CERTFILE_PATH="path/to/the/lnd.cert" +LND_MACAROON_PATH="path/to/the/invoice.macaroon" +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..a97597e --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,264 @@ + +## Install project + +1. Clone the project : + +``` +git clone +``` + +2. go to the folder and install dependencies : +``` +cargo install --path . +``` + +## Available requests + +### Mutation + +There is currently a single mutation available through the API, that allows you to create a post. Note that there is no restriction (like user guard) to create posts. + +Request : + +````graphql +mutation { + createPost(post: { + title: "Ad lorem ipsum", + excerpt: "This is a short description of the post", + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed laoreet suscipit ullamcorper. Etiam sit amet justo dapibus, elementum magna sit amet, faucibus risus. Nullam at augue in quam tristique posuere. Nullam congue dignissim odio non sagittis. Sed in libero erat. Maecenas dictum blandit purus. Suspendisse eget sem suscipit, auctor risus in, ornare orci. Curabitur id facilisis nisl, vitae interdum libero. Aenean commodo nulla sit amet arcu consectetur, non tristique purus elementum. Sed ex sem, blandit eleifend fringilla ac, sagittis auctor ipsum.", + published: true, + price: 100 + }){ + title + excerpt + price + } +} +```` + +will return: + +````json +{ + "data": { + "createPost": { + "title": "Ad lorem ipsum", + "excerpt": "This is a short description of the post", + "price": 100 + } + } +} +```` +### Queries + +#### **Get posts list** + +Request : +```graphql +{ + getPostsList{ + uuid + title + excerpt + price + } +} +``` + +will return something like : + +```json +{ + "data": { + "getPostsList": [ + { + "uuid": "9f3711b4-f733-4911-9863-0c4ee575ca10", + "title": "ad lorem ipsum", + "excerpt": "alea jacta est", + "price": 100 + }, + { + "uuid": "e07677d1-4a45-422e-ac9b-a3a39cd91f0c", + "title": "Ad lorem ipsum", + "excerpt": "This is a short description of the post", + "price": 100 + } + ] + } +} +``` +#### **Get a single post** + +This is the query where the paywall and most of the LN Network interaction is applied. + +You'll find the code block that handles the paywall [here](https://github.com/Asone/graphqln/blob/master/src/graphql/query.rs#L40) + +The request takes an object with two fields : +- The post uuid +- The payment request that should allow the server to identify the access to the content has been paid. This field is optional, and if not provided, the api will respond with an error providing an invoice. + + +For example, providing the request like this : + +```graphql +{ + getPost(post:{ + uuid: "9f3711b4-f733-4911-9863-0c4ee575ca10" + }){ + uuid + title + excerpt + content + price + } +} +``` + +You'll get a response similar to this : + +```json +{ + "data": null, + "errors": [ + { + "message": "Payable post. Payment not found.. Use provided payment request.", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "getPost" + ], + "extensions": { + "payment_request": "lnbc1u1pshhszcpp5e3wpuwldl92zumajqs58k69stru6g9rc43nw0v7uy5rnk8vl7f0sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5ggd3dps9r27dcmxtmj463uct653n2agqttmjhm3qw6wgkfzaqw9s9qyyssq8zga2evqh8lt7kv40269puz3xehezxqvauhz4he0zvyke0x642q33jy85za4qtwa5p24x0vh5ve2p5ztqw64mlpsuwj5ml3ke8rl67spzzhwhv", + "r_hash": "cc5c1e3bedf9542e6fb204287b68b058f9a41478ac66e7b3dc25073b1d9ff25f" + } + } + ] +} +``` + +Note the [extensions](https://github.com/graphql/graphql-spec/blame/main/spec/Section%207%20--%20Response.md#L201-L204) sub-object which are part of the graphQL spec. +It provides a `payment_request` which will allow user to pay to access the content. + +The user can try to refetch the content providing the same `payment_request` within the request. + +```graphql +{ + getPost(post:{ + uuid: "9f3711b4-f733-4911-9863-0c4ee575ca10", + paymentRequest: "lnbc1u1pshhszcpp5e3wpuwldl92zumajqs58k69stru6g9rc43nw0v7uy5rnk8vl7f0sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5ggd3dps9r27dcmxtmj463uct653n2agqttmjhm3qw6wgkfzaqw9s9qyyssq8zga2evqh8lt7kv40269puz3xehezxqvauhz4he0zvyke0x642q33jy85za4qtwa5p24x0vh5ve2p5ztqw64mlpsuwj5ml3ke8rl67spzzhwhv" + }){ + uuid + title + excerpt + content + price + } +} +``` + +When provided, the server will check the invoice state for the provided `payment_request` and its local association with the requested post to ensure the user can access the content. + +A few cases can happen. + +If user requests with another payment request that is not the one provided, it will get a similar response to this : + +```json +{ + "data": null, + "errors": [ + { + "message": "No recorded payment request related to the requested post found with the payment requested provided. Proceed to payment with the provided request payment", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "getPost" + ], + "extensions": { + "payment_request": "lnbc1u1pshh3vypp5zyzf88glqv7gkuvqn95j97nzlt32xk8c9tu8t8dzywyax0vdw72sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5275n2cjqjgly9trkmucfux3krxw9z5na7wjmtklvua0j8tsw0pts9qyyssqw5kcm6r0zp4d5uu0p9zq5ehx9zen4svt63tvj20pa5kwfevv3p7x863f7mz4spa4w6p326jkegjq3gwtf8jzzr72nukyn8aw2s2gayqppmrhmq", + "r_hash": "1104939d1f033c8b7180996922fa62fae2a358f82af8759da22389d33d8d7795" + } + } + ] +} +``` + +Note how the server provides automatically a new invoice so the front-end can provide it to the user. + +If user requests the data without paying but providing the payment request he will get something similar to this : + +```json +{ + "data": null, + "errors": [ + { + "message": "Awaiting for payment to be done.", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "getPost" + ] + } + ] +} +``` + +If invoice expired, user will get something similar to this : + + +```json +{ + "data": null, + "errors": [ + { + "message": "Payment expired or canceled. Proceed to payment with the provided request payment", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "getPost" + ], + "extensions": { + "payment_request": "lnbc1u1pshh3vypp5zyzf88glqv7gkuvqn95j97nzlt32xk8c9tu8t8dzywyax0vdw72sdpagaexzurg29xyugzp2pyjqur0wd6zqcn40ysr5grpvssxcmmjv4kjq6tswd6k6cqzpgxqyz5vqsp5275n2cjqjgly9trkmucfux3krxw9z5na7wjmtklvua0j8tsw0pts9qyyssqw5kcm6r0zp4d5uu0p9zq5ehx9zen4svt63tvj20pa5kwfevv3p7x863f7mz4spa4w6p326jkegjq3gwtf8jzzr72nukyn8aw2s2gayqppmrhmq", + "r_hash": "1104939d1f033c8b7180996922fa62fae2a358f82af8759da22389d33d8d7795" + } + } + ] +} +``` + +Note how the server regenerates automatically a new invoice to be provided to the user. + +Finally, once the user has paid the invoice, he will get the content with a response similar to this : + +```json +{ + "data": { + "getPost": { + "uuid": "9f3711b4-f733-4911-9863-0c4ee575ca10", + "title": "ad lorem ipsum", + "excerpt": "alea jacta est", + "content": "ad lorem ipsum dolor sit amet fluctuat nec mergitur rosa rosae rosam", + "price": 100 + } + } +} +``` + +Note that the user can reuse the payment request as many times as he wants as +we do store the association invoice - content and server will check to the LND instance that the invoice is settled. + diff --git a/docs/paywall.md b/docs/paywall.md new file mode 100644 index 0000000..eeffd46 --- /dev/null +++ b/docs/paywall.md @@ -0,0 +1,39 @@ +# Paywall + +## Principles + +The paywall acts like a bridge beetween a payment network - Lightning network - and the +access rights to a file or a content. + +The bridge can be implemented on different levels. + + +## Globally protected API + +The most basic paywall mechanism is to protect a whole API behind a proof of access. + +It is not implemented on this project. + +## Route protected API + +In this case the client must provide a token - the payment request - to access the endpoint of the API. + +The client uses the headers to provide the token. + +if no token or an invalid one is provided, the client should be redirected to an HTTP 402 response, providing an invoice - the payment request - to the client. + +When the payment request is provided by the client, the server reads the invoice settlement state to provide access to the content. + +## GraphQL query protected API + +On certain queries of a graphQL the client must provide a payment_request value to access the response of the API. + +The client calls the query which will provide the invoice - the payment request - to the client the the graphQL errors property. + +When the payment request is provided as query's input, the server matches resource payment registry and reads the invoice settlement matching it. + +if no token is provided, or an invalid one, +the client will be provided an invoice - the payment request - + + +## GraphQL field protected API \ No newline at end of file diff --git a/juniper_rocket_multipart_handler/src/graphql_upload_operations_request.rs b/juniper_rocket_multipart_handler/src/graphql_upload_operations_request.rs new file mode 100644 index 0000000..83de319 --- /dev/null +++ b/juniper_rocket_multipart_handler/src/graphql_upload_operations_request.rs @@ -0,0 +1,77 @@ +use juniper::{ + http::GraphQLBatchRequest, DefaultScalarValue, GraphQLSubscriptionType, RootNode, ScalarValue, +}; +use juniper::{GraphQLType, GraphQLTypeAsync}; +use juniper_rocket::GraphQLResponse; +use rocket::http::Status; + +/// A GraphQL operations request. +/// +/// This struct replicates the [`GraphQLRequest`](https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/src/lib.rs#L64) original behavior. +/// It is provided and used with the upload wrapper as the original struct +/// does not provide any constructor and the tuple constructor is private. +#[derive(Debug, PartialEq)] +pub struct GraphQLOperationsRequest(pub GraphQLBatchRequest) +where + S: ScalarValue; + +impl GraphQLOperationsRequest +where + S: ScalarValue, +{ + /// Asynchronously execute an incoming GraphQL query. + pub async fn execute( + &self, + root_node: &RootNode<'_, QueryT, MutationT, SubscriptionT, S>, + context: &CtxT, + ) -> GraphQLResponse + where + QueryT: GraphQLTypeAsync, + QueryT::TypeInfo: Sync, + MutationT: GraphQLTypeAsync, + MutationT::TypeInfo: Sync, + SubscriptionT: GraphQLSubscriptionType, + SubscriptionT::TypeInfo: Sync, + CtxT: Sync, + S: Send + Sync, + { + let response = self.0.execute(root_node, context).await; + let status = if response.is_ok() { + Status::Ok + } else { + Status::BadRequest + }; + let json = serde_json::to_string(&response).unwrap(); + + GraphQLResponse(status, json) + } + + /// Synchronously execute an incoming GraphQL query. + pub fn execute_sync( + &self, + root_node: &RootNode, + context: &CtxT, + ) -> GraphQLResponse + where + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, + { + let response = self.0.execute_sync(root_node, context); + let status = if response.is_ok() { + Status::Ok + } else { + Status::BadRequest + }; + let json = serde_json::to_string(&response).unwrap(); + + GraphQLResponse(status, json) + } + + /// Returns the operation names associated with this request. + /// + /// For batch requests there will be multiple names. + pub fn operation_names(&self) -> Vec> { + self.0.operation_names() + } +} diff --git a/juniper_rocket_multipart_handler/src/lib.rs b/juniper_rocket_multipart_handler/src/lib.rs new file mode 100644 index 0000000..e0ad8a4 --- /dev/null +++ b/juniper_rocket_multipart_handler/src/lib.rs @@ -0,0 +1,3 @@ +pub mod graphql_upload_operations_request; +pub mod graphql_upload_wrapper; +pub mod temp_file; diff --git a/migrations/2022-04-16-153823_create_media/down.sql b/migrations/2022-04-16-153823_create_media/down.sql new file mode 100644 index 0000000..8333bd2 --- /dev/null +++ b/migrations/2022-04-16-153823_create_media/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE "media"; \ No newline at end of file diff --git a/migrations/2022-04-16-153823_create_media/up.sql b/migrations/2022-04-16-153823_create_media/up.sql new file mode 100644 index 0000000..3acd7e4 --- /dev/null +++ b/migrations/2022-04-16-153823_create_media/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here-- Your SQL goes here +CREATE TABLE IF NOT EXISTS "media" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "absolute_path" TEXT NOT NULL, + "price" INT NOT NULL DEFAULT 0, + "published" boolean NOT NULL DEFAULT false, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/migrations/2022-04-21-063037_create_media_payment/down.sql b/migrations/2022-04-21-063037_create_media_payment/down.sql new file mode 100644 index 0000000..4eccb48 --- /dev/null +++ b/migrations/2022-04-21-063037_create_media_payment/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE "media_payment"; \ No newline at end of file diff --git a/migrations/2022-04-21-063037_create_media_payment/up.sql b/migrations/2022-04-21-063037_create_media_payment/up.sql new file mode 100644 index 0000000..273eede --- /dev/null +++ b/migrations/2022-04-21-063037_create_media_payment/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here + +CREATE TABLE IF NOT EXISTS "media_payment" ( + "uuid" uuid UNIQUE NOT NULL, + "request" text UNIQUE NOT NULL, + "state" text, + "hash" TEXT UNIQUE NOT NULL, + "media_uuid" uuid references media(uuid) NOT NULL, + "expires_at" TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY( uuid ) +); \ No newline at end of file diff --git a/rocket.toml.dist b/rocket.toml.dist index 637b049..41410ba 100644 --- a/rocket.toml.dist +++ b/rocket.toml.dist @@ -1,3 +1,15 @@ [global.databases] -main_db = { url = "postgres://:@/"} \ No newline at end of file +main_db = { url = "postgres://:@/"} + + [default.limits] +data-form = "32 MiB" + +# Uncomment if you want to use SSL +#[default.tls] +#certs = "ssl/cert.pem" +#key = "ssl/cert-key.pem" + +[release] +log_level="normal" +secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 2b3c2a9..1afe4b3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,73 +2,63 @@ /// Todo : Split routes in different files /// use crate::{ - db::models::user_token::UserToken, - forms::login_user::LoginUser, graphql::{context::GQLContext, mutation::Mutation, query::Query}, guards::userguard::UserGuard, lnd::client::LndClient, }; - -use rocket::{ - form::{Form, Strict}, - http::{Cookie, Status}, - response::content::{self}, - State, -}; +use juniper_rocket_multipart_handler::graphql_upload_wrapper::GraphQLUploadWrapper; +use rocket::State; pub type Schema = RootNode<'static, Query, Mutation, EmptySubscription>; use crate::db::PostgresConn; use crate::guards::paymentrequestheader::PaymentRequestHeader; use juniper::{EmptySubscription, RootNode}; use juniper_rocket::GraphQLResponse; -use rocket::http::CookieJar; - -#[rocket::get("/")] -pub fn graphiql() -> content::Html { - juniper_rocket::graphiql_source("/graphql", None) -} /* This is a void handler that will return a 200 empty response for browsers that intends to check pre-flight for CORS rules. */ #[rocket::options("/graphql")] -pub async fn options_handler() {} +pub async fn graphql_options_handler() {} + +#[rocket::options("/auth")] +pub async fn auth_options_handler() {} /** - Calls the GraphQL API from a HTTP GET Request. - It does nothing special but a paywall mechanism through - a payment_request param could be implemented later. + Calls the API with a query specific paywall protected mechanism. */ -#[rocket::get("/graphql?")] -pub async fn get_graphql_handler( - request: juniper_rocket::GraphQLRequest, +#[rocket::post("/graphql", data = "")] +pub async fn post_graphql_handler( + request: GraphQLUploadWrapper, schema: &State, db: PostgresConn, user_guard: UserGuard, lnd: LndClient, ) -> GraphQLResponse { request + .operations .execute( &*schema, &GQLContext { pool: db, lnd: lnd, + files: request.files, user: user_guard.0, + server_config: None, }, ) .await } -/** - Calls the API with a query specific paywall protected mechanism. -*/ -#[rocket::post("/graphql", data = "")] -pub async fn post_graphql_handler( +/// Calls the API through an API-scoped paywall +#[rocket::post("/payable", data = "")] +pub async fn payable_post_graphql_handler( request: juniper_rocket::GraphQLRequest, schema: &State, db: PostgresConn, - user_guard: UserGuard, lnd: LndClient, + _payment_request: PaymentRequestHeader, + user_guard: UserGuard, ) -> GraphQLResponse { request .execute( @@ -76,53 +66,35 @@ pub async fn post_graphql_handler( &GQLContext { pool: db, lnd: lnd, + files: None, user: user_guard.0, + server_config: None, }, ) .await } -/// Authentication route -#[rocket::post("/auth", data = "")] -pub async fn login( - db: PostgresConn, - cookies: &CookieJar<'_>, - user_form: Form>, -) -> rocket::http::Status { - let user = user_form.into_inner().into_inner(); - - let session = user.login(db).await; - - match session { - Ok(user_session) => { - let token = UserToken::generate_token(user_session).unwrap(); - let cookie = Cookie::build("session", token).finish(); - - cookies.add(cookie); - Status::Ok - } - Err(_) => Status::ExpectationFailed, - } -} - -/// Calls the API through an API-scoped paywall -#[rocket::post("/payable", data = "")] -pub async fn payable_post_graphql_handler( - request: juniper_rocket::GraphQLRequest, +#[rocket::post("/upload", data = "")] +pub async fn upload<'r>( + request: GraphQLUploadWrapper, schema: &State, db: PostgresConn, - lnd: LndClient, - _payment_request: PaymentRequestHeader, user_guard: UserGuard, + lnd: LndClient, ) -> GraphQLResponse { - request + let result = request + .operations .execute( &*schema, &GQLContext { pool: db, - lnd: lnd, + lnd, + files: request.files, user: user_guard.0, + server_config: None, }, ) - .await + .await; + + result } diff --git a/src/catchers/payment_required.rs b/src/catchers/payment_required.rs index fb177a4..a075cc6 100644 --- a/src/catchers/payment_required.rs +++ b/src/catchers/payment_required.rs @@ -1,7 +1,7 @@ use crate::db::models::api_payment::ApiPayment; use crate::db::PostgresConn; use crate::lnd::client::LndClient; -use rocket::response::content::Json; +use rocket::response::content::RawJson; use rocket::response::status; use rocket::{catch, http::Status, Request}; @@ -13,7 +13,7 @@ use rocket::{catch, http::Status, Request}; pub async fn payment_required<'r>( _: Status, request: &'r Request<'_>, -) -> Result>, Status> { +) -> Result>, Status> { let pool = request.guard::().await.succeeded(); let lnd_client_result = request.guard::().await.succeeded(); @@ -31,7 +31,7 @@ pub async fn payment_required<'r>( Ok(payment_request) => { let json_state = format!(r#"{{"payment": {:?}}}"#, payment_request.request.as_str()); - Ok(status::Custom(Status::PaymentRequired, Json(json_state))) + Ok(status::Custom(Status::PaymentRequired, RawJson(json_state))) } Err(_) => Err(Status::InternalServerError), } diff --git a/src/cors.rs b/src/cors.rs index 9029844..66d148d 100644 --- a/src/cors.rs +++ b/src/cors.rs @@ -1,13 +1,27 @@ use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Header; use rocket::{Request, Response}; +use std::env; +/// Represents a Cors Config object +/// that will be used to build Cors Policy on +/// Server runtime +struct CorsConfig { + allow_origin: String, + allow_methods: String, + allow_headers: String, + allow_credentials: String, +} + +/// +/// Allows us to modify CORS default parameters +/// pub struct Cors; -/** - Allows us to modify CORS default parameters -*/ #[rocket::async_trait] +/// +/// Implements the Cors Policy +/// impl Fairing for Cors { fn info(&self) -> Info { Info { @@ -16,13 +30,45 @@ impl Fairing for Cors { } } + // Fairing provides the cors policy on request response async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { - response.set_header(Header::new("Access-Control-Allow-Origin", "*")); + // Gets the config + let config = Cors::get_config(); + + response.set_header(Header::new( + "Access-Control-Allow-Origin", + config.allow_origin, + )); response.set_header(Header::new( "Access-Control-Allow-Methods", - "POST, GET, PATCH, OPTIONS", + config.allow_methods, + )); + response.set_header(Header::new( + "Access-Control-Allow-Headers", + config.allow_headers, )); - response.set_header(Header::new("Access-Control-Allow-Headers", "*")); - response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); + response.set_header(Header::new( + "Access-Control-Allow-Credentials", + config.allow_credentials, + )); + } +} + +impl Cors { + // Creates a config with environment variables if set and default values for + // values not set in env variables + fn get_config() -> CorsConfig { + let origin_policy = env::var("CORS_ORIGIN_POLICY").unwrap_or("*".to_string()); + let allow_methods = + env::var("CORS_METHOD_POLICY").unwrap_or("POST, GET, PATCH, OPTIONS".to_string()); + let allow_headers = env::var("CORS_HEADERS_POLICY").unwrap_or("*".to_string()); + let allow_credentials = env::var("CORS_CREDENTIALS_POLICY").unwrap_or("false".to_string()); + + return CorsConfig { + allow_origin: origin_policy.to_owned(), + allow_methods: allow_methods.to_owned(), + allow_headers: allow_headers.to_owned(), + allow_credentials: allow_credentials.to_owned(), + }; } } diff --git a/src/db/igniter.rs b/src/db/igniter.rs new file mode 100644 index 0000000..30a610f --- /dev/null +++ b/src/db/igniter.rs @@ -0,0 +1,32 @@ +use std::env; + +use super::PostgresConn; +use rocket::{Build, Rocket}; +embed_migrations!(); +/// This method should be called on ignite of the server - juste before server launch -. +/// It calls the migrations scripts to populate the database with tables and default data. +/// Note that it is up to the SQL scripts in migrations to ensure that it does not override +/// previous existing tables or data when executed. +pub async fn run_db_migrations(rocket: Rocket) -> Result, Rocket> { + let conn = PostgresConn::get_one(&rocket) + .await + .expect("Database connection"); + + + + let flag = env::var("DATABASE_RUN_MIGRATIONS_ON_IGNITE").unwrap_or("false".to_string()).parse::(); + + // Skip + if flag.is_err() || !flag.unwrap() { + return Ok(rocket); + } + + conn.run(|conn| match embedded_migrations::run(&*conn) { + Ok(()) => Ok(rocket), + Err(e) => { + error!("Failed to run database migrations: {:?}", e); + Err(rocket) + } + }) + .await +} diff --git a/src/db/mod.rs b/src/db/mod.rs index b8e1d7e..21905c8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -2,6 +2,7 @@ use rocket_sync_db_pools::{database, diesel}; use self::models::api_payment::ApiPayment; +pub mod igniter; pub mod models; pub mod schema; diff --git a/src/db/models/media.rs b/src/db/models/media.rs new file mode 100644 index 0000000..8f44a4d --- /dev/null +++ b/src/db/models/media.rs @@ -0,0 +1,73 @@ +pub use crate::db::schema::media; + +use crate::graphql::types::input::file::FileInput; +use chrono::NaiveDateTime; +use diesel; +use diesel::prelude::*; +use diesel::PgConnection; +use std::path::PathBuf; + +#[derive(Identifiable, Queryable, PartialEq, Debug)] +#[primary_key(uuid)] +#[table_name = "media"] +pub struct Media { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub absolute_path: String, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[derive(Debug, Insertable)] +#[table_name = "media"] +pub struct NewMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub absolute_path: String, + pub published: bool, + pub price: i32, +} + +impl From<(&PathBuf, FileInput)> for NewMedia { + fn from(file_data: (&PathBuf, FileInput)) -> Self { + Self { + uuid: uuid::Uuid::new_v4(), + title: file_data.1.title, + description: file_data.1.description, + absolute_path: file_data.0.to_string_lossy().to_owned().to_string(), + price: file_data.1.price, + published: file_data.1.published, + } + } +} + +impl Media { + pub fn create(new_media: NewMedia, connection: &PgConnection) -> QueryResult { + use crate::db::schema::media::dsl::*; + + diesel::insert_into::(media) + .values(&new_media) + .get_result(connection) + } + + pub fn find_all_published(connection: &PgConnection) -> Vec { + use crate::db::schema::media::dsl::*; + media.filter(published.eq(true)).load(connection).unwrap() + } + + pub fn find_one_by_uuid( + media_uuid: uuid::Uuid, + connection: &PgConnection, + ) -> QueryResult> { + use crate::db::schema::media::dsl::*; + + media + .filter(uuid.eq(media_uuid)) + .first::(connection) + .optional() + } +} diff --git a/src/db/models/media_payment.rs b/src/db/models/media_payment.rs new file mode 100644 index 0000000..5bdfc84 --- /dev/null +++ b/src/db/models/media_payment.rs @@ -0,0 +1,65 @@ +pub use crate::db::schema::media_payment; +use crate::lnd::invoice::LndInvoice; +use chrono::NaiveDateTime; +use diesel; +use diesel::prelude::*; +use diesel::PgConnection; +use uuid::Uuid; + +#[derive(Queryable, PartialEq, Associations, Debug)] +#[table_name = "media_payment"] +#[belongs_to(parent = Media, foreign_key = "media_uuid")] +pub struct MediaPayment { + pub uuid: Uuid, + pub request: String, + pub state: Option, + pub hash: String, + pub media_uuid: Uuid, + pub expires_at: NaiveDateTime, +} + +#[derive(Debug, Insertable)] +#[table_name = "media_payment"] +pub struct NewMediaPayment { + uuid: Uuid, + hash: String, + request: String, + media_uuid: Uuid, + expires_at: NaiveDateTime, +} + +impl From<(LndInvoice, uuid::Uuid)> for NewMediaPayment { + fn from(data: (LndInvoice, uuid::Uuid)) -> Self { + Self { + uuid: Uuid::new_v4(), + hash: data.0.r_hash, + request: data.0.payment_request, + media_uuid: data.1.to_owned(), + expires_at: data.0.expires_at, + } + } +} + +impl MediaPayment { + pub fn find_one_by_request( + payment_request: String, + connection: &PgConnection, + ) -> QueryResult> { + use crate::db::schema::media_payment::dsl::*; + media_payment + .filter(request.eq(payment_request)) + .first::(connection) + .optional() + } + + pub fn create( + new_payment: NewMediaPayment, + connection: &PgConnection, + ) -> QueryResult { + use crate::db::schema::media_payment::dsl::*; + + diesel::insert_into::(media_payment) + .values(&new_payment) + .get_result(connection) + } +} diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 7553841..31e7baa 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -1,4 +1,6 @@ pub mod api_payment; +pub mod media; +pub mod media_payment; pub mod payment; pub mod post; pub mod session; diff --git a/src/db/models/payment.rs b/src/db/models/payment.rs index b882e1f..73dd941 100644 --- a/src/db/models/payment.rs +++ b/src/db/models/payment.rs @@ -1,4 +1,5 @@ pub use crate::db::schema::payment; +use crate::graphql::types::output::payment::PaymentType; use crate::lnd::invoice::LndInvoice; use chrono::NaiveDateTime; use diesel; @@ -26,6 +27,7 @@ pub struct NewPayment { request: String, post_uuid: Uuid, expires_at: NaiveDateTime, + state: Option, } impl From<(LndInvoice, uuid::Uuid)> for NewPayment { @@ -36,6 +38,7 @@ impl From<(LndInvoice, uuid::Uuid)> for NewPayment { request: data.0.payment_request, post_uuid: data.1, expires_at: data.0.expires_at, + state: Some(PaymentType::state_from_invoice_state(data.0.state)), } } } diff --git a/src/db/schema.rs b/src/db/schema.rs index 279d700..a4e6615 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -8,6 +8,30 @@ table! { } } +table! { + media (uuid) { + uuid -> Uuid, + title -> Text, + description -> Nullable, + absolute_path -> Text, + price -> Int4, + published -> Bool, + created_at -> Timestamptz, + updated_at -> Timestamptz, + } +} + +table! { + media_payment (uuid) { + uuid -> Uuid, + request -> Text, + state -> Nullable, + hash -> Text, + media_uuid -> Uuid, + expires_at -> Timestamptz, + } +} + table! { payment (uuid) { uuid -> Uuid, @@ -52,7 +76,16 @@ table! { } } +joinable!(media_payment -> media (media_uuid)); joinable!(payment -> post (post_uuid)); joinable!(session -> user (user_uuid)); -allow_tables_to_appear_in_same_query!(api_payment, payment, post, session, user,); +allow_tables_to_appear_in_same_query!( + api_payment, + media, + media_payment, + payment, + post, + session, + user, +); diff --git a/src/graphql/context.rs b/src/graphql/context.rs index f230e74..2c138c8 100644 --- a/src/graphql/context.rs +++ b/src/graphql/context.rs @@ -1,10 +1,14 @@ +use std::collections::HashMap; + use crate::{ db::{models::user::User, PostgresConn}, lnd::client::LndClient, }; use derive_more::Deref; -use tonic::codegen::InterceptedService; +use juniper_rocket_multipart_handler::temp_file::TempFile; +// use tonic::codegen::InterceptedService; +use tonic_lnd::tonic::codegen::InterceptedService; use tonic_lnd::{rpc::lightning_client::LightningClient, MacaroonInterceptor}; /* @@ -20,7 +24,9 @@ pub struct GQLContext { #[deref] pub pool: PostgresConn, pub lnd: LndClient, + pub files: Option>, pub user: Option, + pub server_config: Option, } impl juniper::Context for GQLContext {} @@ -45,6 +51,10 @@ impl GQLContext { return &self.pool; } + pub fn get_files(&self) -> &Option> { + return &self.files; + } + /// Provides the instance of optional user pub fn get_user(&self) -> &Option { return &self.user; diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index a774c0c..a44c086 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -1,5 +1,6 @@ pub mod context; pub mod mutation; pub mod paywall_context; +pub mod queries; pub mod query; pub mod types; diff --git a/src/graphql/mutation.rs b/src/graphql/mutation.rs index d2a82fe..d3d5364 100644 --- a/src/graphql/mutation.rs +++ b/src/graphql/mutation.rs @@ -1,13 +1,17 @@ use juniper::{FieldError, FieldResult}; use crate::db::models::{ + media::{Media, NewMedia}, post::{NewPost, Post}, user::User, }; use super::{ context::GQLContext, - types::{input::post::CreatePostInput, output::post::PostType}, + types::{ + input::{file::FileInput, post::CreatePostInput}, + output::{media::MediaType, post::PostType}, + }, }; pub struct Mutation; @@ -51,6 +55,68 @@ impl Mutation { } } + #[graphql(description = "Upload and stores a payable media onto the server")] + async fn upload_file<'a>( + context: &'a GQLContext, + file_input: FileInput, + ) -> FieldResult { + if Self::is_authenticated(&context.user) == false { + return Err(FieldError::new( + "You need to be authenticated to use this mutation", + graphql_value!(""), + )); + } + + let files_map = context.get_files(); + let connection = context.get_db_connection(); + + match files_map { + Some(files_map) => { + + if files_map.len() == 0 { + return Err(FieldError::new( + "Current mutation requires a single file for upload. No file provided", + graphql_value!("") + )) + } + + let file = files_map.into_iter().next(); + + match file { + Some(file) => { + let persisted_path = file.1.persist_file(); + + match persisted_path { + Ok(path) => { + let new_media = NewMedia::from((&path,file_input)); + let media = connection.run(move |c| Media::create(new_media,c)).await; + match media { + Ok(media) => Ok(MediaType::from(media)), + Err(_) => Err(FieldError::new( + "Error while persisting file. Aborting", + graphql_value!("") + )) + } + }, + Err(_) => Err(FieldError::new("Error while writing file on filesystem.", + graphql_value!("") + )) + } + + }, + None => Err(FieldError::new( + "Current mutation requires a single file for upload. No file provided", + graphql_value!("") + )) + } + }, + None => Err(FieldError::new( + "Current mutation accepts a single file for upload. Multiple files uploaded provided", + graphql_value!("") + )) + } + } + /// Changes password for current user async fn change_password<'a>(context: &'a GQLContext, password: String) -> FieldResult { if Self::is_authenticated(&context.user) == false { diff --git a/src/graphql/paywall_context.rs b/src/graphql/paywall_context.rs index 48a6bef..efa3f4f 100644 --- a/src/graphql/paywall_context.rs +++ b/src/graphql/paywall_context.rs @@ -2,7 +2,8 @@ use crate::{db::PostgresConn, lnd::client::LndClient}; use derive_more::Deref; -use tonic::codegen::InterceptedService; +// use tonic::codegen::InterceptedService; +use tonic_lnd::tonic::codegen::InterceptedService; use tonic_lnd::{rpc::lightning_client::LightningClient, MacaroonInterceptor}; /* diff --git a/src/graphql/queries/get_files_list.rs b/src/graphql/queries/get_files_list.rs new file mode 100644 index 0000000..1d80a52 --- /dev/null +++ b/src/graphql/queries/get_files_list.rs @@ -0,0 +1,14 @@ +use crate::db::models::media::Media; +use crate::graphql::context::GQLContext; +use crate::graphql::types::output::media::MediaType; +use juniper::FieldError; + +pub async fn get_files_list<'a>(context: &'a GQLContext) -> Result, FieldError> { + let connection = context.get_db_connection(); + let db_results = connection.run(move |c| Media::find_all_published(c)).await; + + Ok(db_results + .into_iter() + .map(|p| MediaType::from(p)) + .collect::>()) +} diff --git a/src/graphql/queries/get_media.rs b/src/graphql/queries/get_media.rs new file mode 100644 index 0000000..75c2e74 --- /dev/null +++ b/src/graphql/queries/get_media.rs @@ -0,0 +1,30 @@ +use crate::db::models::media::Media; +use crate::graphql::context::GQLContext; +use crate::graphql::types::output::media::MediaType; +use juniper::{FieldError, Value}; +use uuid::Uuid; + +pub async fn get_media<'a, 'b>( + context: &'a GQLContext, + uuid: Uuid, + payment_request: Option, +) -> Result { + let connection = context.get_db_connection(); + let result = connection + .run(move |c| Media::find_one_by_uuid(uuid, c)) + .await; + + match result { + Ok(result) => match result { + Some(media) => match payment_request { + Some(payment_request) => Ok(MediaType::from((media, payment_request))), + None => Ok(MediaType::from(media)), + }, + None => Err(FieldError::new( + "No media found with the provided Uuid", + Value::null(), + )), + }, + Err(_) => Err(FieldError::new("Error while fetching media", Value::null())), + } +} diff --git a/src/graphql/queries/get_post.rs b/src/graphql/queries/get_post.rs new file mode 100644 index 0000000..4d08faa --- /dev/null +++ b/src/graphql/queries/get_post.rs @@ -0,0 +1,84 @@ +use super::utils::QueryUtils; +use crate::db::models::payment::Payment; +use crate::graphql::types::input::post::PayablePostInput; +use crate::graphql::types::output::post::PostType; +use crate::lnd::invoice::InvoiceUtils; +use crate::{db::models::Post, graphql::context::GQLContext}; +use juniper::{FieldError, Value}; +use tonic_lnd::rpc::invoice::InvoiceState; + +pub async fn get_post<'a, 'b>( + context: &'a GQLContext, + post: PayablePostInput, +) -> Result { + let post_id: uuid::Uuid = post.uuid.clone(); + let connection = context.get_db_connection(); + + // Find the post in the database + let result = connection + .run(move |c| Post::find_one_by_id(post_id, c)) + .await; + + match result { + Some(r) => match r.published { + // Checks if post is published + true => match r.is_payable() { + // Checks if there should be a paywall ( price > 0 ) + true => match post.payment_request { + // If payable, ensure there's a payment_request provided + Some(payment_request) => { + // payment_request found + + // Search for payment entry based on the payment_request provided + let payment = connection + .run(move |c| Payment::find_one_by_request(payment_request.clone(), c)) + .await; + match payment { + Some(payment) => { // Payment found + + // Request LND invoice and checks the invoice state + match InvoiceUtils::get_invoice_state_from_payment_request(context.get_lnd_client(), payment.request).await { + Ok(invoice_result) => match invoice_result { + Some(invoice) => match invoice.state() { + InvoiceState::Settled => Ok(PostType::from(r)), // Payment has been done. Serves the post + InvoiceState::Open => Err(FieldError::new( + "Awaiting for payment to be done.", + graphql_value!({"state": "open"}), + )), // Payment hasn't been done yet. We shall wait for payment, so there's no need to regenerate an invoice + InvoiceState::Accepted => Err(FieldError::new( + "Payment ongoing but not settled yet", + graphql_value!({"state": "ongoing"}), + )), // Payment is on process onto the network but has not reach its receiver yet. We shall wait, so there's no need to regenerate an invoice + InvoiceState::Canceled => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"Payment expired or canceled.").await), + }, + // LND Server says there's no invoice matching + None => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"No invoice found for corresponding payment request. Proceed to payment with the provided request payment").await) + + }, + // Invoice is broken. Maybe we should serve a new invoice here ? + Err(_) => Err(FieldError::new( + "An error happened when trying to decode invoice", + Value::null(), + )), + } + }, + // Our DB does not contain any payment with the provided payment_request. + None => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"No recorded payment request related to the requested post found with the payment requested provided.").await) + } + } + None => Err(QueryUtils::generate_invoiced_error( + context, + post_id, + r, + "Payable post. Payment not found.", + ) + .await), + }, + false => Ok(PostType::from(r)), // Post has a price of 0 (free), so we serve it without condition + }, + false => Err(FieldError::new("Post not found", Value::Null)), // Post not published + }, + // Post has not been found in DB + None => Err(FieldError::new("Post not found", Value::Null)), + } +} diff --git a/src/graphql/queries/get_posts_list.rs b/src/graphql/queries/get_posts_list.rs new file mode 100644 index 0000000..f8d0464 --- /dev/null +++ b/src/graphql/queries/get_posts_list.rs @@ -0,0 +1,15 @@ +use crate::graphql::types::output::post::PreviewPostType; +use crate::{db::models::Post, graphql::context::GQLContext}; +use juniper::FieldError; + +pub async fn get_posts_list<'a>( + context: &'a GQLContext, +) -> Result, FieldError> { + let connection = context.get_db_connection(); + let db_results = connection.run(move |c| Post::find_all_published(c)).await; + + Ok(db_results + .into_iter() + .map(|p| PreviewPostType::from(p)) + .collect::>()) +} diff --git a/src/graphql/queries/mod.rs b/src/graphql/queries/mod.rs new file mode 100644 index 0000000..b7e9964 --- /dev/null +++ b/src/graphql/queries/mod.rs @@ -0,0 +1,7 @@ +pub mod get_files_list; +pub mod get_media; +pub mod get_post; +pub mod get_posts_list; +pub mod request_invoice_for_media; +pub mod request_invoice_for_post; +pub mod utils; diff --git a/src/graphql/queries/request_invoice_for_media.rs b/src/graphql/queries/request_invoice_for_media.rs new file mode 100644 index 0000000..41e2702 --- /dev/null +++ b/src/graphql/queries/request_invoice_for_media.rs @@ -0,0 +1,215 @@ +use crate::db::models::media::Media; +use crate::db::models::media_payment::MediaPayment; +use crate::db::models::media_payment::NewMediaPayment; +use crate::db::PostgresConn; +use crate::graphql::context::GQLContext; +use crate::graphql::types::output::invoices::MediaInvoice; +use crate::lnd::invoice::InvoiceParams; +use crate::lnd::invoice::InvoiceUtils; +use juniper::{FieldError, Value}; +use tonic::codegen::InterceptedService; +use tonic::transport::Channel; +use tonic_lnd::rpc::invoice::InvoiceState; +use tonic_lnd::MacaroonInterceptor; + +use tonic_lnd::rpc::lightning_client::LightningClient; + +/// Requests an invoice and/or its state for a media. +/// The request can get an optional `payment_request` +/// that if provided will have its validity checked. +pub async fn request_invoice_for_media<'a>( + context: &'a GQLContext, + uuid: uuid::Uuid, + payment_request: Option, +) -> Result { + let connection = context.get_db_connection(); + let client = context.get_lnd_client(); + + // Get media from db + let db_result = connection + .run(move |c| Media::find_one_by_uuid(uuid, c)) + .await; + + // db failure + if db_result.is_err() { + return Err(FieldError::new( + "Error while requesting database", + Value::Null, + )); + } + + // unwrap result if no db failure + let media = db_result.unwrap(); + + // Return error if there is no media found in db + if media.is_none() { + return Err(FieldError::new( + "No media found with provided uuid", + Value::Null, + )); + } + + // unwrap result to get the media. At this point we are sure + // media is some due to if statement above + let media = media.unwrap(); + + // Dispatch action based on presence of payment_request in request input + match payment_request { + Some(payment_request) => { + check_provided_payment_request(connection, client.clone(), media, payment_request).await + } + None => create_media_invoice(connection, client, media).await, + } +} + +async fn create_media_invoice( + connection: &PostgresConn, + lnd: &LightningClient>, + media: Media, +) -> Result { + let payment = generate_media_payment(connection, lnd, media).await; + match payment { + Ok(payment) => Ok(MediaInvoice::from((payment, InvoiceState::Open))), + Err(_) => Err(FieldError::new( + "Error while registering payment request.", + Value::Null, + )), + } +} + +/// Processes a check of an invoice state when payment_request input field is provided +async fn check_provided_payment_request( + connection: &PostgresConn, + lnd: LightningClient>, + media: Media, + payment_request: String, +) -> Result { + // Request db to find payment + let payment = connection + .run(move |c| MediaPayment::find_one_by_request(payment_request, c)) + .await; + + // In case of db failure + if payment.is_err() { + return Err(FieldError::new( + "Error while requesting database", + Value::Null, + )); + } + + // Unwrap result + let payment = payment.unwrap(); + + // In case there is no payment found + if payment.is_none() { + return Err(FieldError::new( + "No payment found with the provided payment_request", + Value::Null, + )); + } + + // Unwrap as we are sure at this point there is a payment + let payment = payment.unwrap(); + + // Ensure the request media is the same that is associated in the payment + if payment.media_uuid != media.uuid { + return Err(FieldError::new( + "payment_request does not match with the request media", + Value::Null, + )); + } + let payment_request = payment.request.clone(); + let invoice_result = + InvoiceUtils::get_invoice_state_from_payment_request(&lnd, payment_request).await; + + // Request LND service to get the Invoice object + + // In case of LND Service failure + if invoice_result.is_err() { + return Err(FieldError::new( + "Error while requesting lightning network registry", + Value::Null, + )); + } + + // unwrap the result as we are sure error case is handled + let invoice = invoice_result.unwrap(); + + // In case no invoice is found on LND service + if invoice.is_none() { + return Err(FieldError::new( + "No invoice found with the current payment request on the lightning network service", + Value::Null, + )); + } + + // Unwrap as we are sure at this point we have an invoice + let invoice = invoice.unwrap(); + + // Return result based on the invoice state + match invoice.state() { + InvoiceState::Accepted => { + // If invoice is in accepted state we return the current media invoice + // with the current state. + Ok(MediaInvoice::from((payment, invoice.state()))) + } + InvoiceState::Canceled => { + // If invoice has been canceled we generate a new one + let payment = generate_media_payment(connection, &lnd, media).await; + + // We catch the result and return the new media payment. + // We provide invoice state for previous invoice as + // this will help returning the replacementpayment output type + match payment { + Ok(payment) => Ok(MediaInvoice::from((payment, InvoiceState::Canceled))), + Err(error) => Err(error), + } + } + InvoiceState::Open => Ok(MediaInvoice::from((payment, invoice.state()))), + InvoiceState::Settled => Ok(MediaInvoice::from((payment, invoice.state()))), + } +} + +/// Method to generate a media payment +/// With invoice registering on LND +async fn generate_media_payment( + connection: &PostgresConn, + lnd: &LightningClient>, + media: Media, +) -> Result { + let memo = format!("Buy file \"{}\" with uuid: {}", media.title, media.uuid); + let params = InvoiceParams::new(Some(media.price as i64), Some(memo), None); + let invoice = InvoiceUtils::generate_invoice(lnd.clone(), params).await; + let payment = connection + .run(move |c| MediaPayment::create(NewMediaPayment::from((invoice, media.uuid)), c)) + .await; + + match payment { + Ok(payment) => Ok(payment), + Err(_) => Err(FieldError::new( + "Error while registering payment request.", + Value::Null, + )), + } +} + +/// Method to generate a field error +/// from a media payment +/// Shall be deleted as not used anymore +fn _field_error_from_media_payment( + media_payment: MediaPayment, + message: Option, +) -> FieldError { + let message = message.unwrap_or("Payment required".to_string()); + let request = media_payment.request.as_str(); + let expires_at = media_payment.expires_at.timestamp() as i32; + let state = media_payment.state; + FieldError::new( + message, + graphql_value!({ + "state": state, + "paymentRequest": request, + "expiresAt": expires_at + }), + ) +} diff --git a/src/graphql/queries/request_invoice_for_post.rs b/src/graphql/queries/request_invoice_for_post.rs new file mode 100644 index 0000000..5d064cf --- /dev/null +++ b/src/graphql/queries/request_invoice_for_post.rs @@ -0,0 +1,38 @@ +use crate::db::models::payment::NewPayment; +use crate::db::models::payment::Payment; +use crate::graphql::types::output::payment::PaymentType; +use crate::lnd::invoice::InvoiceUtils; +use crate::{db::models::Post, graphql::context::GQLContext}; +use juniper::{FieldError, Value}; + +pub async fn request_invoice_for_post<'a>( + context: &'a GQLContext, + post_id: uuid::Uuid, +) -> Result { + let connection = context.get_db_connection(); + let db_result = connection + .run(move |c| Post::find_one_by_id(post_id, c)) + .await; + + match db_result { + Some(post) => { + let invoice = + InvoiceUtils::generate_post_invoice(context.get_lnd_client().clone(), post).await; + let payment = connection + .run(move |c| Payment::create(NewPayment::from((invoice, post_id)), c)) + .await; + + match payment { + Ok(payment) => Ok(PaymentType::from(payment)), + Err(_) => Err(FieldError::new( + "Could not find post with provided uuid", + Value::Null, + )), + } + } + None => Err(FieldError::new( + "Could not find post with provided uuid", + Value::Null, + )), + } +} diff --git a/src/graphql/queries/utils.rs b/src/graphql/queries/utils.rs new file mode 100644 index 0000000..fcf029b --- /dev/null +++ b/src/graphql/queries/utils.rs @@ -0,0 +1,45 @@ +use crate::db::models::payment::NewPayment; +use crate::db::models::payment::Payment; +use crate::lnd::invoice::InvoiceUtils; +use crate::{db::models::Post, graphql::context::GQLContext}; +use juniper::{FieldError, Value}; +use uuid::Uuid; + +pub struct QueryUtils {} + +impl QueryUtils { + pub async fn generate_invoiced_error( + context: &GQLContext, + post_id: Uuid, + post: Post, + message: &str, + ) -> FieldError { + let connection = context.get_db_connection(); + let invoice = + InvoiceUtils::generate_post_invoice(context.get_lnd_client().clone(), post).await; + let payment = connection + .run(move |c| Payment::create(NewPayment::from((invoice, post_id)), c)) + .await; + + match payment { + Ok(payment) => { + let request = payment.request.as_str(); + let hash = payment.hash.as_str(); + + FieldError::new( + format!("{} Use provided payment request.", message), + graphql_value!({"state": "open", + "payment_request": request, + "r_hash": hash}), + ) + } + Err(_) => FieldError::new( + format!( + "{}. An error happened while trying to generate payment request", + message + ), + Value::null(), + ), + } + } +} diff --git a/src/graphql/query.rs b/src/graphql/query.rs index 0bee28e..cf1c071 100644 --- a/src/graphql/query.rs +++ b/src/graphql/query.rs @@ -1,15 +1,19 @@ +use super::queries::get_files_list::get_files_list; +use super::queries::get_media::get_media; +use super::queries::get_post::get_post; +use super::queries::get_posts_list::get_posts_list; +use super::queries::request_invoice_for_media::request_invoice_for_media; +use super::queries::request_invoice_for_post::request_invoice_for_post; use super::types::input::post::PayablePostInput; +use super::types::output::media::MediaType; use super::types::output::payment::PaymentType; use super::types::output::post::PostType; use super::types::output::post::PreviewPostType; -use crate::db::models::payment::NewPayment; -use crate::db::models::payment::Payment; -use crate::lnd::invoice::InvoiceUtils; -use crate::{db::models::Post, graphql::context::GQLContext}; -use juniper::{FieldError, Value}; -use tonic_lnd::rpc::invoice::InvoiceState; +use crate::db::models::media::Media; +use crate::graphql::context::GQLContext; +use crate::graphql::types::output::invoices::MediaInvoice; +use juniper::FieldError; use uuid::Uuid; - pub struct Query; #[juniper::graphql_object(context = GQLContext)] @@ -21,49 +25,47 @@ impl Query { */ #[graphql(description = "Retrieves the list of posts")] async fn get_posts_list(context: &'a GQLContext) -> Result, FieldError> { - let connection = context.get_db_connection(); - let db_results = connection.run(move |c| Post::find_all_published(c)).await; + get_posts_list(context).await + } - Ok(db_results - .into_iter() - .map(|p| PreviewPostType::from(p)) - .collect::>()) + #[graphql(description = "Requests list of files")] + async fn get_files_list(context: &'a GQLContext) -> Result, FieldError> { + get_files_list(context).await + } + + #[graphql(description = " + Requests an invoice for a media. \n + If a payment_request is provided, the query will check + for the provided payment_request status and provide a new onee + if necessary. + ")] + async fn request_invoice_for_media( + context: &'a GQLContext, + uuid: uuid::Uuid, + payment_request: Option, + ) -> Result { + request_invoice_for_media(context, uuid, payment_request).await } #[graphql(description = "Requests a ln query paywall invoice for a given post")] - async fn requestInvoiceForPost( + async fn request_invoice_for_post( context: &'a GQLContext, post_id: uuid::Uuid, ) -> Result { - let connection = context.get_db_connection(); - let db_result = connection - .run(move |c| Post::find_one_by_id(post_id, c)) - .await; - - match db_result { - Some(post) => { - let invoice = - InvoiceUtils::generate_post_invoice(context.get_lnd_client().clone(), post) - .await; - let payment = connection - .run(move |c| Payment::create(NewPayment::from((invoice, post_id)), c)) - .await; - - match payment { - Ok(payment) => Ok(PaymentType::from(payment)), - Err(_) => Err(FieldError::new( - "Could not find post with provided uuid", - Value::Null, - )), - } - } - None => Err(FieldError::new( - "Could not find post with provided uuid", - Value::Null, - )), - } + request_invoice_for_post(context, post_id).await } + /* + * + */ + #[graphql(description = "Gets a specific post. The query is protected through a paywall")] + async fn get_media<'a, 'b>( + context: &'a GQLContext, + uuid: Uuid, + payment_request: Option, + ) -> Result { + get_media(context, uuid, payment_request).await + } /* Gets a post. This is the main request where paywall shall be applied. @@ -73,116 +75,17 @@ impl Query { context: &'a GQLContext, post: PayablePostInput, ) -> Result { - let post_id: uuid::Uuid = post.uuid.clone(); - let connection = context.get_db_connection(); - - // Find the post in the database - let result = connection - .run(move |c| Post::find_one_by_id(post_id, c)) - .await; - - match result { - Some(r) => match r.published { - // Checks if post is published - true => match r.is_payable() { - // Checks if there should be a paywall ( price > 0 ) - true => match post.payment_request { - // If payable, ensure there's a payment_request provided - Some(payment_request) => { - // payment_request found - - // Search for payment entry based on the payment_request provided - let payment = connection - .run(move |c| { - Payment::find_one_by_request(payment_request.clone(), c) - }) - .await; - match payment { - Some(payment) => { // Payment found - - // Request LND invoice and checks the invoice state - match InvoiceUtils::get_invoice_state_from_payment_request(context.get_lnd_client(), payment.request).await { - Ok(invoice_result) => match invoice_result { - Some(invoice) => match invoice.state() { - InvoiceState::Settled => Ok(PostType::from(r)), // Payment has been done. Serves the post - InvoiceState::Open => Err(FieldError::new( - "Awaiting for payment to be done.", - graphql_value!({"state": "open"}), - )), // Payment hasn't been done yet. We shall wait for payment, so there's no need to regenerate an invoice - InvoiceState::Accepted => Err(FieldError::new( - "Payment ongoing but not settled yet", - graphql_value!({"state": "ongoing"}), - )), // Payment is on process onto the network but has not reach its receiver yet. We shall wait, so there's no need to regenerate an invoice - InvoiceState::Canceled => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"Payment expired or canceled.").await), - }, - // LND Server says there's no invoice matching - None => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"No invoice found for corresponding payment request. Proceed to payment with the provided request payment").await) - - }, - // Invoice is broken. Maybe we should serve a new invoice here ? - Err(_) => Err(FieldError::new( - "An error happened when trying to decode invoice", - Value::null(), - )), - } - }, - // Our DB does not contain any payment with the provided payment_request. - None => Err(QueryUtils::generate_invoiced_error(context,post_id,r,"No recorded payment request related to the requested post found with the payment requested provided.").await) - } - } - None => Err(QueryUtils::generate_invoiced_error( - context, - post_id, - r, - "Payable post. Payment not found.", - ) - .await), - }, - false => Ok(PostType::from(r)), // Post has a price of 0 (free), so we serve it without condition - }, - false => Err(FieldError::new("Post not found", Value::Null)), // Post not published - }, - // Post has not been found in DB - None => Err(FieldError::new("Post not found", Value::Null)), - } + get_post(context, post).await } -} - -pub struct QueryUtils {} -impl QueryUtils { - pub async fn generate_invoiced_error( - context: &GQLContext, - post_id: Uuid, - post: Post, - message: &str, - ) -> FieldError { + #[graphql(description = "Gets the list of available medias")] + async fn get_medias_list(context: &'a GQLContext) -> Result, FieldError> { let connection = context.get_db_connection(); - let invoice = - InvoiceUtils::generate_post_invoice(context.get_lnd_client().clone(), post).await; - let payment = connection - .run(move |c| Payment::create(NewPayment::from((invoice, post_id)), c)) - .await; + let db_results = connection.run(move |c| Media::find_all_published(c)).await; - match payment { - Ok(payment) => { - let request = payment.request.as_str(); - let hash = payment.hash.as_str(); - - FieldError::new( - format!("{} Use provided payment request.", message), - graphql_value!({"state": "open", - "payment_request": request, - "r_hash": hash}), - ) - } - Err(_) => FieldError::new( - format!( - "{}. An error happened while trying to generate payment request", - message - ), - Value::null(), - ), - } + Ok(db_results + .into_iter() + .map(|media| MediaType::from(media)) + .collect::>()) } } diff --git a/src/graphql/types/input/file.rs b/src/graphql/types/input/file.rs new file mode 100644 index 0000000..81748b9 --- /dev/null +++ b/src/graphql/types/input/file.rs @@ -0,0 +1,11 @@ +#[derive(Clone, GraphQLInputObject)] +pub struct FileInput { + pub filename: String, + pub title: String, + pub description: Option, + pub price: i32, + pub published: bool, + // We expect this to be always `null` as per the spec + // see : https://github.com/jaydenseric/graphql-multipart-request-spec + pub file: Option, +} diff --git a/src/graphql/types/input/mod.rs b/src/graphql/types/input/mod.rs index e8b6291..34681e3 100644 --- a/src/graphql/types/input/mod.rs +++ b/src/graphql/types/input/mod.rs @@ -1 +1,2 @@ +pub mod file; pub mod post; diff --git a/src/graphql/types/output/invoices.rs b/src/graphql/types/output/invoices.rs new file mode 100644 index 0000000..ed57637 --- /dev/null +++ b/src/graphql/types/output/invoices.rs @@ -0,0 +1,76 @@ +use chrono::NaiveDateTime; +use tonic_lnd::rpc::invoice::InvoiceState; +use uuid::Uuid; + +use crate::db::models::media_payment::MediaPayment; + +#[derive(GraphQLObject)] +pub struct AvailablePayment { + #[graphql(description = "The related media uuid")] + media_uuid: Uuid, + #[graphql(description = "The paywall ln invoice payment request string")] + payment_request: String, + #[graphql(description = "The expiry time of current invoice")] + expires_at: NaiveDateTime, + #[graphql(description = "The current state of the payment request")] + state: Option, +} + +#[derive(GraphQLObject)] +pub struct ReplacementPayment { + #[graphql(description = "The related media uuid")] + media_uuid: Uuid, + #[graphql(description = "The paywall ln invoice payment request string")] + payment_request: String, + #[graphql(description = "The expiry time of current invoice")] + expires_at: NaiveDateTime, + #[graphql(description = "The current state of the payment request")] + state: Option, +} + +#[derive(GraphQLObject)] +pub struct SettledPayment { + #[graphql(description = "The related media uuid")] + media_uuid: Uuid, + #[graphql(description = "The paywall ln invoice payment request string")] + payment_request: String, + #[graphql(description = "The current state of the payment request")] + state: Option, +} + +#[derive(GraphQLUnion)] +pub enum MediaInvoice { + ReplacementPayment(ReplacementPayment), + AvailablePayment(AvailablePayment), + SettledPayment(SettledPayment), +} + +impl From<(MediaPayment, InvoiceState)> for MediaInvoice { + fn from(data: (MediaPayment, InvoiceState)) -> Self { + match data.1 { + InvoiceState::Accepted => Self::AvailablePayment(AvailablePayment { + media_uuid: data.0.media_uuid, + payment_request: data.0.request, + expires_at: data.0.expires_at, + state: Some("accepted".to_string()), + }), + InvoiceState::Open => Self::AvailablePayment(AvailablePayment { + media_uuid: data.0.media_uuid, + payment_request: data.0.request, + expires_at: data.0.expires_at, + state: Some("open".to_string()), + }), + InvoiceState::Settled => Self::SettledPayment(SettledPayment { + media_uuid: data.0.media_uuid, + payment_request: data.0.request, + state: Some("settled".to_string()), + }), + InvoiceState::Canceled => Self::ReplacementPayment(ReplacementPayment { + media_uuid: data.0.media_uuid, + payment_request: data.0.request, + expires_at: data.0.expires_at, + state: Some("open".to_string()), + }), + } + } +} diff --git a/src/graphql/types/output/media.rs b/src/graphql/types/output/media.rs new file mode 100644 index 0000000..c0f7f0c --- /dev/null +++ b/src/graphql/types/output/media.rs @@ -0,0 +1,167 @@ +use std::env; + +use crate::{ + db::models::{ + media::Media, + media_payment::{MediaPayment, NewMediaPayment}, + }, + graphql::context::GQLContext, + lnd::invoice::{InvoiceParams, InvoiceUtils}, +}; +use chrono::NaiveDateTime; +use infer::Infer; +use juniper::Value; +use juniper::{FieldError, FieldResult}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct MediaPreviewType { + pub uuid: uuid::Uuid, + pub description: Option, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} + +#[derive(Clone)] +pub struct MediaType { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, + absolute_path: String, + payment_request: Option, +} + +impl From for MediaType { + fn from(item: Media) -> Self { + Self { + uuid: item.uuid, + title: item.title, + description: item.description, + price: item.price, + published: item.published, + created_at: item.created_at, + absolute_path: item.absolute_path, + payment_request: None, + } + } +} + +impl From<(Media, String)> for MediaType { + fn from(item: (Media, String)) -> Self { + let media = item.0; + + Self { + uuid: media.uuid, + title: media.title, + description: media.description, + price: media.price, + published: media.published, + created_at: media.created_at, + absolute_path: media.absolute_path, + payment_request: Some(item.1), + } + } +} + +impl MediaType { + async fn _generate_invoiced_error(&self, context: &GQLContext, message: &str) -> FieldError { + let connection = context.get_db_connection(); + let params = InvoiceParams::new(Some(self.price.into()), None, None); + let invoice = + InvoiceUtils::generate_invoice(context.get_lnd_client().clone(), params).await; + let uuid = self.uuid.clone(); + // let invoice = + // InvoiceUtils::generate_post_invoice(context.get_lnd_client().clone(), post).await; + let media_payment = connection + .run(move |c| MediaPayment::create(NewMediaPayment::from((invoice, uuid)), c)) + .await; + + match media_payment { + Ok(media_payment) => { + let request = media_payment.request.as_str(); + let hash = media_payment.hash.as_str(); + + FieldError::new( + format!("{} Use provided payment request.", message), + graphql_value!({"state": "open", + "payment_request": request, + "r_hash": hash}), + ) + } + Err(_) => FieldError::new( + format!( + "{}. An error happened while trying to generate payment request", + message + ), + Value::null(), + ), + } + } +} + +#[graphql_object( + name = "Media", + description = "Full Media output type" + context = GQLContext +)] +impl MediaType { + #[graphql(description = "The media internal id")] + fn uuid(&self) -> uuid::Uuid { + self.uuid + } + + #[graphql(description = "Media's title")] + fn title(&self) -> &String { + &self.title + } + + #[graphql(description = "Description of media")] + fn description(&self) -> Option<&String> { + self.description.as_ref() + } + + #[graphql(description = "Price of media access in satoshis. If free is 0")] + fn price(&self) -> i32 { + self.price + } + + #[graphql(description = "Publication status of media")] + fn published(&self) -> bool { + self.published + } + + #[graphql(description = "Creation date of media")] + fn created_at(&self) -> NaiveDateTime { + self.created_at + } + + #[graphql(description = "The direct URL of a media.")] + fn absolute_path(&self) -> String { + let a = env::current_dir().unwrap(); + + a.to_string_lossy().to_string() + } + + #[graphql(description = "the public URL to a media")] + fn public_url<'a>(&self, _context: &'a GQLContext) -> FieldResult { + let uri = format!("/file/{}", &self.uuid); + Ok(uri) + } + #[graphql(description = "The file type")] + fn file_type(&self) -> Option<&str> { + let info = Infer::new(); + let kind = info.get_from_path(&self.absolute_path); + + match kind { + Ok(result) => match result { + Some(t) => return Some(t.extension()), + None => return None, + }, + Err(_) => return None, + } + } +} diff --git a/src/graphql/types/output/mod.rs b/src/graphql/types/output/mod.rs index 835f9a2..79cd724 100644 --- a/src/graphql/types/output/mod.rs +++ b/src/graphql/types/output/mod.rs @@ -1,2 +1,4 @@ +pub mod invoices; +pub mod media; pub mod payment; pub mod post; diff --git a/src/graphql/types/output/payment.rs b/src/graphql/types/output/payment.rs index 22a71c5..b3603a2 100644 --- a/src/graphql/types/output/payment.rs +++ b/src/graphql/types/output/payment.rs @@ -1,5 +1,6 @@ -use crate::db::models::payment::Payment; +use crate::db::models::{media_payment::MediaPayment, payment::Payment}; use chrono::NaiveDateTime; +use tonic_lnd::rpc::invoice::InvoiceState; #[derive(GraphQLObject)] #[graphql(description = "A payment request object")] @@ -8,6 +9,8 @@ pub struct PaymentType { payment_request: String, #[graphql(description = "The expiry time of current invoice")] expires_at: NaiveDateTime, + #[graphql(description = "The current state of the payment request")] + state: Option, } impl From for PaymentType { @@ -15,6 +18,48 @@ impl From for PaymentType { Self { payment_request: item.request, expires_at: item.expires_at, + state: None, + } + } +} + +impl From for PaymentType { + fn from(item: MediaPayment) -> Self { + Self { + payment_request: item.request, + expires_at: item.expires_at, + state: None, + } + } +} + +impl From<(MediaPayment, InvoiceState)> for PaymentType { + fn from(item: (MediaPayment, InvoiceState)) -> Self { + Self { + payment_request: item.0.request, + expires_at: item.0.expires_at, + state: Some(Self::state_from_invoice_state(item.1)), + } + } +} + +impl From<(MediaPayment, &InvoiceState)> for PaymentType { + fn from(item: (MediaPayment, &InvoiceState)) -> Self { + Self { + payment_request: item.0.request, + expires_at: item.0.expires_at, + state: Some(Self::state_from_invoice_state(*item.1)), + } + } +} + +impl PaymentType { + pub fn state_from_invoice_state(invoice_state: InvoiceState) -> String { + match invoice_state { + InvoiceState::Accepted => String::from("accepted"), + InvoiceState::Canceled => String::from("canceled"), + InvoiceState::Settled => String::from("settled"), + InvoiceState::Open => String::from("open"), } } } diff --git a/src/guards/userguard.rs b/src/guards/userguard.rs index c52241a..f56028d 100644 --- a/src/guards/userguard.rs +++ b/src/guards/userguard.rs @@ -47,14 +47,14 @@ impl<'r> FromRequest<'r> for UserGuard { async fn from_request(request: &'r Request<'_>) -> Outcome { let authorization = request.headers().get_one("Authorization"); - - match authorization { - Some(authorization) => { - let formated_token = Self::format_bearer(authorization); + let session = request.cookies().get("session"); + match session { + Some(session) => { + // let formated_token = Self::format_bearer(authorization); let secret = Self::get_secret().unwrap(); let token = jsonwebtoken::decode::( - formated_token.as_str(), + session.value(), &DecodingKey::from_secret(secret.as_ref()), &Validation::new(Algorithm::HS256), ); diff --git a/src/lnd/invoice.rs b/src/lnd/invoice.rs index 9de9782..4226bc1 100644 --- a/src/lnd/invoice.rs +++ b/src/lnd/invoice.rs @@ -3,10 +3,12 @@ use crate::db::models::Post; use chrono::{Duration, NaiveDateTime, Utc}; use std::env; -use tonic::codegen::InterceptedService; +use tonic_lnd::rpc::invoice::InvoiceState; +// use tonic::codegen::InterceptedService; use tonic::{Code, Status}; use tonic_lnd::rpc::lightning_client::LightningClient; use tonic_lnd::rpc::{Invoice, PaymentHash}; +use tonic_lnd::tonic::codegen::InterceptedService; use tonic_lnd::MacaroonInterceptor; use lightning_invoice::*; @@ -21,9 +23,8 @@ pub struct InvoiceParams { impl InvoiceParams { pub fn new(value: Option, memo: Option, expiry: Option) -> Self { let default_value = env::var("DEFAULT_INVOICE_VALUE").unwrap_or("100".to_string()); - let default_expiry = - env::var("DEFAULT_INVOICE_EXPIRY").unwrap_or("API Payment".to_string()); - let default_memo = env::var("DEFAULT_INVOICE_MEMO").unwrap_or("600".to_string()); + let default_expiry = env::var("DEFAULT_INVOICE_EXPIRY").unwrap_or("600".to_string()); + let default_memo = env::var("DEFAULT_INVOICE_MEMO").unwrap_or("API Payment".to_string()); Self { value: value.unwrap_or_else(|| default_value.parse::().unwrap()), @@ -45,6 +46,7 @@ pub struct LndInvoice { pub value: i64, pub r_hash: String, pub expires_at: NaiveDateTime, + pub state: InvoiceState, } impl LndInvoice { @@ -55,12 +57,15 @@ impl LndInvoice { let expires_at = Utc::now() .checked_add_signed(Duration::seconds(invoice.expiry)) .unwrap(); + let state = invoice.state(); + Self { payment_request: invoice.payment_request, memo: invoice.memo, value: invoice.value as i64, r_hash: r_hash, expires_at: expires_at.naive_utc(), + state: state, } } } @@ -128,12 +133,58 @@ impl InvoiceUtils { LndInvoice::new(invoice, hex::encode(result.r_hash)) } - /* - Gets the invoice state from a payment request string. - It consists as a two steps method. - - First it registers an invoice - */ + // pub async fn state_invoice<'a>( + // lnd_client: &LightningClient< + // InterceptedService, + // >, + // payment_request: String) -> Result { + // let mut client = lnd_client.clone(); + + // // Parse the payment request + // let invoice = payment_request + // .as_str() + // .parse::() + // .unwrap(); + + // // Get the payment hash + // let p_hash = invoice.payment_hash().unwrap(); + + // /* + // The below instruction might seems a bit odd. + // the expected r_hash here is not the Invoice r_hash + // but rather the r_hash of the payment request which is + // denominated in the SignedRawInvoice as the payment_hash. + // */ + // let request = tonic::Request::new(PaymentHash { + // r_hash: p_hash.0.to_vec(), + // ..PaymentHash::default() + // }); + + // match client.lookup_invoice(request).await { + // Ok(response) => { + // match response.into_inner().state() { + // InvoiceState::Open => Err(Status::PaymentRequired), + // InvoiceState::Settled => Ok(Status::Ok), + // InvoiceState::Canceled => Err(Status::PaymentRequired), + // InvoiceState::Accepted => Err(Status::Accepted), + // } + // } , + // Err(status) => { + // if status.code() == Code::Unknown + // && (status.message() == "there are no existing invoices" + // || status.message() == "unable to locate invoice") + // { + // Ok(None) + // } else { + // Err(status) + // } + // } + // } + // } + // } + + // Gets the invoice state from a payment request string. + // It consists as a two steps method. pub async fn get_invoice_state_from_payment_request<'a>( lnd_client: &LightningClient< InterceptedService, diff --git a/src/main.rs b/src/main.rs index 2502378..1315b79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,14 @@ extern crate rocket; #[macro_use] extern crate diesel; +#[macro_use] +extern crate diesel_migrations; + extern crate diesel_derive_enum; extern crate dotenv; +extern crate juniper_rocket_multipart_handler; +extern crate tokio_util; extern crate tonic; - mod app; mod catchers; mod cors; @@ -21,16 +25,20 @@ mod forms; mod graphql; mod guards; mod lnd; +mod routes; +use crate::db::PostgresConn; +use app::Schema; +use db::igniter::run_db_migrations; use dotenv::dotenv; use juniper::EmptySubscription; -use rocket::Rocket; +use rocket::fairing::AdHoc; +use rocket::{Build, Rocket}; +use routes::{auth::login, file::get_file, utils::graphiql}; -use crate::db::PostgresConn; -use app::Schema; use app::{ - get_graphql_handler, graphiql, login, options_handler, payable_post_graphql_handler, - post_graphql_handler, + auth_options_handler, graphql_options_handler, payable_post_graphql_handler, + post_graphql_handler, upload, }; use catchers::payment_required::payment_required; use cors::Cors; @@ -46,13 +54,20 @@ itconfig::config! { } #[rocket::main] -async fn main() { +async fn main() -> Result<(), rocket::Error> { dotenv().ok(); config::init(); - Rocket::build() - .register("/", catchers![payment_required]) + let _rocket = Rocket::build() .attach(PostgresConn::fairing()) + .attach(Cors) + .attach(AdHoc::try_on_ignite( + "Database Migrations", + run_db_migrations, + )) + .manage(Cors) + // .configure(figment) + .register("/", catchers![payment_required]) .manage(Schema::new( Query, Mutation, @@ -61,17 +76,19 @@ async fn main() { .mount( "/", rocket::routes![ - options_handler, + graphql_options_handler, + auth_options_handler, graphiql, - get_graphql_handler, post_graphql_handler, payable_post_graphql_handler, - login + upload, + login, + get_file ], ) - .attach(Cors) - .manage(Cors) .launch() .await .expect("server to launch"); + + Ok(()) } diff --git a/src/routes/auth.rs b/src/routes/auth.rs new file mode 100644 index 0000000..4821887 --- /dev/null +++ b/src/routes/auth.rs @@ -0,0 +1,35 @@ +use rocket::{ + form::{Form, Strict}, + http::{Cookie, CookieJar, SameSite, Status}, +}; + +use crate::{ + db::{models::user_token::UserToken, PostgresConn}, + forms::login_user::LoginUser, +}; + +/// Authentication route +#[rocket::post("/auth", data = "")] +pub async fn login( + db: PostgresConn, + cookies: &CookieJar<'_>, + user_form: Form>, +) -> rocket::http::Status { + let user = user_form.into_inner().into_inner(); + + let session = user.login(db).await; + + match session { + Ok(user_session) => { + let token = UserToken::generate_token(user_session).unwrap(); + let cookie = Cookie::build("session", token) + .same_site(SameSite::None) + .secure(true) + .finish(); + + cookies.add(cookie); + Status::Ok + } + Err(_) => Status::ExpectationFailed, + } +} diff --git a/src/routes/file.rs b/src/routes/file.rs new file mode 100644 index 0000000..2c91404 --- /dev/null +++ b/src/routes/file.rs @@ -0,0 +1,234 @@ +use rocket::{ + fs::NamedFile, + http::Status, + response::{content::RawJson, status}, +}; +use tonic::{codegen::InterceptedService, transport::Channel}; +use tonic_lnd::{ + rpc::{invoice::InvoiceState, lightning_client::LightningClient, Invoice}, + MacaroonInterceptor, +}; +use uuid::Uuid; + +use crate::{ + db::{ + models::{ + media::Media, + media_payment::{MediaPayment, NewMediaPayment}, + }, + PostgresConn, + }, + lnd::{ + client::LndClient, + invoice::{InvoiceParams, InvoiceUtils}, + }, +}; + +#[derive(Debug)] +pub enum FileHandlingError { + MediaNotFound, + InvoiceNotFound, + DbFailure, + LNFailure, + UuidParsingError, + PaymentRequired, +} + +/// A route to retrieve files behind the paywall. +#[rocket::get("/file/?")] +pub async fn get_file( + uuid: String, + invoice: Option, + db: PostgresConn, + lnd: LndClient, +) -> Result>>> { + // Calls the get_media to try to retrieve the requested media from database + let media = get_media(&uuid, &db).await; + + // Builds the error response if the media could not be retrieved + if media.is_err() { + return match media.unwrap_err() { + FileHandlingError::DbFailure => Err(status::Custom(Status::InternalServerError, None)), + FileHandlingError::MediaNotFound => Err(status::Custom(Status::NotFound, None)), + FileHandlingError::UuidParsingError => Err(status::Custom(Status::BadRequest, None)), + _ => Err(status::Custom(Status::ImATeapot, None)), + }; + } + + // There's no reason we could not unwrap the media as the match above should ensure to handle all the error cases. + // We can so consider the unwrap as safe. + let media = media.unwrap(); + + // If the media exists and is free we should deliver it to the user without performing any further operation + if media.price == 0 { + return Ok(NamedFile::open(media.absolute_path).await.unwrap()); + } + + // Otherwise we ensure try to retrieve an associated payment to the requested media. + // see get_media_payment for handling process + let payment = get_media_payment(invoice, &media.uuid, &db).await; + + // Payment retrieval failed + if payment.is_err() { + return match payment.unwrap_err() { + FileHandlingError::DbFailure => Err(status::Custom(Status::InternalServerError, None)), + FileHandlingError::PaymentRequired => { + let invoice = request_new_media_payment(&media, lnd, db).await; + + match invoice { + Ok(invoice) => { + let data = format!("{{ payment_request: {}}}", invoice.request); + + Err(status::Custom(Status::PaymentRequired, Some(RawJson(data)))) + } + Err(e) => match e { + FileHandlingError::DbFailure => { + Err(status::Custom(Status::InternalServerError, None)) + } + _ => Err(status::Custom(Status::ImATeapot, None)), + }, + } + } + _ => Err(status::Custom(Status::ImATeapot, None)), + }; + } + + let invoice = get_invoice(payment.unwrap(), &lnd.0).await; // .map_err(|error| return error).unwrap(); + + if invoice.is_err() { + return match invoice.unwrap_err() { + FileHandlingError::InvoiceNotFound => Err(status::Custom(Status::NotFound, None)), + FileHandlingError::LNFailure => Err(status::Custom(Status::InternalServerError, None)), + _ => Err(status::Custom(Status::ImATeapot, None)), + }; + } + + let invoice = invoice.unwrap(); + + match invoice.state() { + InvoiceState::Settled => Ok(NamedFile::open(media.absolute_path).await.unwrap()), + InvoiceState::Accepted => Err(status::Custom(Status::NotFound, None)), + InvoiceState::Canceled => { + let invoice = request_new_media_payment(&media, lnd, db).await; + match invoice { + Ok(invoice) => { + let data = format!("{{ payment_request: {}}}", invoice.request); + + Err(status::Custom(Status::PaymentRequired, Some(RawJson(data)))) + } + Err(e) => match e { + FileHandlingError::DbFailure => { + Err(status::Custom(Status::InternalServerError, None)) + } + _ => Err(status::Custom(Status::ImATeapot, None)), + }, + } + } + InvoiceState::Open => { + let data = format!("{{ payment_request: {}}}", invoice.payment_request); + + Err(status::Custom(Status::PaymentRequired, Some(RawJson(data)))) + } + } +} + +/// Generates an invoice and saves its value in databasee +async fn request_new_media_payment( + media: &Media, + lnd_client: LndClient, + db: PostgresConn, +) -> Result { + // Loads the client + let client = lnd_client.0; + + // let uuid = Uuid::parse_str(uuid.as_str()); + + // Return error if uuid parsing fails. + // if uuid.is_err() { + // return Err(FileHandlingError::UuidParsingError); + // } + + let uuid = media.uuid.to_owned(); + + // Calls utility to generate an invoice/ + // Todo : Generate + let invoice = InvoiceUtils::generate_invoice( + client, + InvoiceParams::new(Some(media.price.into()), None, None), + ) + .await; + + let media_payment = db + .run(move |c| MediaPayment::create(NewMediaPayment::from((invoice, uuid)), c)) + .await; + + match media_payment { + Ok(media_payment) => Ok(media_payment), + Err(_) => Err(FileHandlingError::DbFailure), + } +} + +// Retrieves media from database +async fn get_media(uuid: &String, db: &PostgresConn) -> Result { + let uuid = Uuid::parse_str(uuid.as_str()); + + match uuid { + Ok(uuid) => { + let media = db.run(move |c| Media::find_one_by_uuid(uuid, c)).await; + match media { + Ok(media) => match media { + Some(media) => Ok(media), + None => Err(FileHandlingError::MediaNotFound), + }, + Err(_) => Err(FileHandlingError::DbFailure), + } + } + Err(_) => Err(FileHandlingError::UuidParsingError), + } +} + +// Retrieves a media payment based on +async fn get_media_payment( + payment_request: Option, + media_uuid: &Uuid, + db: &PostgresConn, +) -> Result { + match payment_request { + // Ensure there is some payment_request provided + Some(payment_request) => { + // Retrieve recorded payment request from db + let payment = db + .run(move |c| MediaPayment::find_one_by_request(payment_request, c)) + .await; + match payment { + Ok(payment) => { + match payment { + Some(payment) => { + // Ensure the retrieved payment request matched the requested file association + match &payment.media_uuid == media_uuid { + true => Ok(payment), + false => Err(FileHandlingError::MediaNotFound), + } + } + None => Err(FileHandlingError::PaymentRequired), + } + } + Err(_) => Err(FileHandlingError::DbFailure), + } + } + None => Err(FileHandlingError::PaymentRequired), + } +} + +async fn get_invoice( + media_payment: MediaPayment, + lnd: &LightningClient>, +) -> Result { + match InvoiceUtils::get_invoice_state_from_payment_request(lnd, media_payment.request).await { + Ok(invoice) => match invoice { + Some(invoice) => Ok(invoice), + None => Err(FileHandlingError::InvoiceNotFound), + }, + Err(_) => Err(FileHandlingError::LNFailure), + } +} diff --git a/src/routes/graphql.rs b/src/routes/graphql.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..2b3fa05 --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,3 @@ +pub mod auth; +pub mod file; +pub mod utils; diff --git a/src/routes/utils.rs b/src/routes/utils.rs new file mode 100644 index 0000000..4e98952 --- /dev/null +++ b/src/routes/utils.rs @@ -0,0 +1,6 @@ +use rocket::response::content; + +#[rocket::get("/")] +pub fn graphiql() -> content::RawHtml { + juniper_rocket::graphiql_source("/graphql", None) +}