From 877a374f97e6eabc5ceb3f46543458b963229ab8 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 18 Jun 2023 21:21:49 -0400 Subject: [PATCH] custviwe --- backend/Cargo.lock | 655 +++++++++++++++++- backend/Cargo.toml | 3 +- backend/src/db_custview.rs | 17 + backend/src/db_orders.rs | 11 +- backend/src/main.rs | 40 +- backend/src/route_customer.rs | 94 +++ backend/src/route_order.rs | 236 ++++++- backend/src/route_products.rs | 28 +- backend/src/route_stripe.rs | 135 ++++ frontend/src/lib/stores/AuthStore.ts | 3 + frontend/src/routes/+page.svelte | 271 +++++++- frontend/src/routes/customer/+page.svelte | 96 +++ frontend/src/routes/manage/env/+page.svelte | 85 +++ .../src/routes/manage/orders/+page.svelte | 22 +- .../src/routes/manage/products/+page.svelte | 11 +- 15 files changed, 1650 insertions(+), 57 deletions(-) create mode 100644 backend/src/db_custview.rs create mode 100644 backend/src/route_customer.rs create mode 100644 backend/src/route_stripe.rs create mode 100644 frontend/src/lib/stores/AuthStore.ts create mode 100644 frontend/src/routes/customer/+page.svelte diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b342f31..061c539 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -72,7 +72,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash 0.8.3", - "base64", + "base64 0.21.2", "bitflags", "brotli", "bytes", @@ -91,7 +91,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.8.5", "sha1", "smallvec", "tokio", @@ -281,7 +281,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -293,7 +293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -322,6 +322,27 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "arc-bytes" version = "0.3.5" @@ -356,6 +377,43 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-stripe" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b313de0d654c4c4c46faa737b2257ce9ed79e69926aa4c734b9816be20102b2c" +dependencies = [ + "chrono", + "futures-util", + "hex", + "hmac 0.12.1", + "http-types", + "hyper", + "hyper-tls", + "serde", + "serde_json", + "serde_path_to_error", + "serde_qs 0.10.1", + "sha2 0.10.7", + "smart-default", + "smol_str", + "thiserror", + "time-core", + "tokio", + "uuid", +] + [[package]] name = "async-trait" version = "0.1.68" @@ -422,6 +480,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.2" @@ -562,7 +626,7 @@ dependencies = [ "p256", "parking_lot", "pot", - "rand", + "rand 0.8.5", "region", "serde", "sysinfo", @@ -683,6 +747,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "cipher" version = "0.3.0" @@ -756,6 +833,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.6.2" @@ -785,6 +871,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -874,7 +970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1010,7 +1106,7 @@ checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ "der", "elliptic-curve", - "hmac", + "hmac 0.11.0", "signature", ] @@ -1031,7 +1127,7 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1045,19 +1141,49 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1090,6 +1216,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -1147,6 +1288,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1199,6 +1355,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1208,7 +1375,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1241,7 +1408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1303,6 +1470,18 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.11.0" @@ -1310,7 +1489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" dependencies = [ "digest 0.9.0", - "hmac", + "hmac 0.11.0", ] [[package]] @@ -1323,6 +1502,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hpke" version = "0.8.0" @@ -1338,7 +1526,7 @@ dependencies = [ "hkdf", "p256", "paste", - "rand_core", + "rand_core 0.6.4", "serde", "sha2 0.9.9", "subtle", @@ -1356,6 +1544,38 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel", + "base64 0.13.1", + "futures-lite", + "http", + "infer", + "pin-project-lite", + "rand 0.7.3", + "serde", + "serde_json", + "serde_qs 0.8.5", + "serde_urlencoded", + "url", +] + [[package]] name = "httparse" version = "1.8.0" @@ -1368,6 +1588,66 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1394,6 +1674,32 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1445,12 +1751,19 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lmspos" version = "0.1.0" dependencies = [ "actix-cors", "actix-web", + "async-stripe", "bonsaidb", "log", "serde", @@ -1565,7 +1878,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -1575,7 +1888,25 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom", + "getrandom 0.2.10", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1655,6 +1986,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-varint" version = "1.0.1" @@ -1684,6 +2059,12 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1714,7 +2095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1874,6 +2255,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -1881,8 +2275,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1892,7 +2296,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1901,7 +2314,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1979,18 +2401,64 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -2028,6 +2496,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_qs" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2091,7 +2590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" dependencies = [ "digest 0.9.0", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2121,6 +2620,26 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "smol_str" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" +dependencies = [ + "serde", +] + [[package]] name = "socket2" version = "0.4.9" @@ -2203,6 +2722,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2240,9 +2773,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", "libc", @@ -2254,15 +2787,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -2312,6 +2845,16 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -2343,6 +2886,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -2404,6 +2953,12 @@ dependencies = [ "transmog", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "trybuild" version = "1.0.80" @@ -2475,14 +3030,51 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2586,6 +3178,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 46887ad..63fc027 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,4 +11,5 @@ simple_logger = "4" actix-web = "4" bonsaidb = { version = "0.4", features = ["local-full"] } serde = { version = "1", features = ["derive"] } -actix-cors = "0.6.4" \ No newline at end of file +actix-cors = "0.6.4" +async-stripe = { version = "0.22", features = ["runtime-tokio-hyper"] } \ No newline at end of file diff --git a/backend/src/db_custview.rs b/backend/src/db_custview.rs new file mode 100644 index 0000000..a7cf406 --- /dev/null +++ b/backend/src/db_custview.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; +use bonsaidb::core::schema::Collection; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Collection, Clone)] +#[collection(name = "customerviews")] +pub struct CustomerView { + pub sync_id: String, + pub products: HashMap, + pub price_total: f64 +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +pub struct CustomerViewProductSlice { + pub name: String, + pub price_usd: u64 +} \ No newline at end of file diff --git a/backend/src/db_orders.rs b/backend/src/db_orders.rs index 6a7d063..d16c2c9 100644 --- a/backend/src/db_orders.rs +++ b/backend/src/db_orders.rs @@ -7,11 +7,20 @@ use serde::{Deserialize, Serialize}; pub struct Order { pub order_type: OrderType, pub total_usd: f64, - pub products: HashMap + pub products: HashMap, + pub stripe_status: StripeStatus } #[derive(Debug, Serialize, Deserialize, Clone)] pub enum OrderType { Cash, Stripe +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum StripeStatus { + SessionCreated, + Paid, + Failed, + NotStripeTransaction } \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 7dc1dc7..885d304 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,28 +1,38 @@ use std::error::Error; use actix_cors::Cors; -use actix_web::{App, get, HttpResponse, HttpServer}; +use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer}; +use actix_web::http::header::HeaderValue; use actix_web::web::Data; use bonsaidb::core::schema::Schema; use bonsaidb::local::AsyncDatabase; use bonsaidb::local::config::{Builder, StorageConfiguration}; +use stripe::Client; use crate::db_products::Product; use crate::db_orders::Order; -use crate::route_order::{cash_order, get_orders}; +use crate::error::APIError; +use crate::route_customer::{custview_get, custview_push}; +use crate::db_custview::CustomerView; +use crate::route_order::{cash_order, get_order, get_orders, stripe_order}; use crate::route_products::{create_product, delete_product, get_product, get_products, update_product}; +use crate::route_stripe::{stripe_cancel, stripe_finish}; pub mod db_products; pub mod route_products; pub mod error; pub mod route_order; pub mod db_orders; +pub mod route_stripe; +pub mod db_custview; +pub mod route_customer; #[derive(Debug, Schema)] -#[schema(name = "lmsposschema", collections = [Product, Order])] +#[schema(name = "lmsposschema", collections = [Product, Order, CustomerView])] pub struct DbSchema; pub struct AppState { - pub db: AsyncDatabase + pub db: AsyncDatabase, + pub client: Client } #[actix_web::main] @@ -31,8 +41,11 @@ async fn main() -> Result<(), Box> { let db = AsyncDatabase::open::(StorageConfiguration::new("lms.bonsaidb")).await?; + let client = stripe::Client::new("sk_test_51HcdjVJZz7Qxr83OUKOUmzK07dLlpzmbtlUbbxptdGxdXoECKK0BfDo0F89A7A3EFLON8xQDSPxhowVlq0UF2P7000hR3OYha7"); + let state = Data::new(AppState { - db + db, + client }); HttpServer::new(move || { @@ -47,8 +60,15 @@ async fn main() -> Result<(), Box> { .service(status) .service(get_orders) .service(cash_order) + .service(stripe_order) + .service(get_order) + .service(stripe_cancel) + .service(stripe_finish) + .service(auth_status) + .service(custview_push) + .service(custview_get) }) - .bind(("127.0.0.1", 8080))? + .bind(("0.0.0.0", 8080))? .run() .await?; Ok(()) @@ -57,4 +77,12 @@ async fn main() -> Result<(), Box> { #[get("/status")] pub async fn status() -> HttpResponse { HttpResponse::Ok().body("{\"status\":\"ok\"}") +} + +#[get("/auth_status")] +pub async fn auth_status(req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } + HttpResponse::Ok().body("{\"status\":\"ok\"}") } \ No newline at end of file diff --git a/backend/src/route_customer.rs b/backend/src/route_customer.rs new file mode 100644 index 0000000..7e39d48 --- /dev/null +++ b/backend/src/route_customer.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; +use actix_web::{get, HttpRequest, HttpResponse, post}; +use actix_web::http::header::HeaderValue; +use actix_web::web::{Data, Json, Path}; +use bonsaidb::core::Error; +use bonsaidb::core::schema::SerializedCollection; +use log::debug; +use serde::Deserialize; +use crate::AppState; +use crate::db_custview::{CustomerView, CustomerViewProductSlice}; +use crate::error::APIError; + +#[derive(Deserialize)] +pub struct CustviewPushReq { + pub sync_id: String, + pub products: HashMap, + pub price_total: f64 +} + +#[post("/custview")] +pub async fn custview_push(req: Json, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } + + let mut existing_db = match CustomerView::list_async(0.., &db.db).await { + Ok(r) => r, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() }) + } + }; + + let mut converted_hashmap = HashMap::new(); + + for (k, (v, v2)) in &req.products { + converted_hashmap.insert(v.clone(), *v2); + } + + for maybe_db in &mut existing_db { + if maybe_db.contents.sync_id == req.sync_id { + let mut definitely_db = maybe_db; + definitely_db.contents.products = converted_hashmap.clone(); + definitely_db.contents.price_total = req.price_total; + match definitely_db.update_async(&db.db).await { + Ok(_) => (), + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() }) + } + } + return HttpResponse::Ok().finish(); + } + } + + let doc = CustomerView { + sync_id: req.sync_id.clone(), + products: converted_hashmap.clone(), + price_total: req.price_total, + }; + + match doc.push_into_async(&db.db).await { + Ok(_) => (), + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() }) + } + } + + HttpResponse::Ok().finish() +} + +#[get("/custview/{id}")] +pub async fn custview_get(id: Path, db: Data) -> HttpResponse { + let existing_db = match CustomerView::list_async(0.., &db.db).await { + Ok(r) => r, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() }) + } + }; + + for maybe_db in &existing_db { + if maybe_db.contents.sync_id == id.clone() { + let definitely_db = maybe_db; + + let mut translated: HashMap = HashMap::new(); + + for (k, v) in &definitely_db.contents.products { + translated.insert(translated.len() as u64, (k.clone(), *v)); + } + + return HttpResponse::Ok().json(translated.clone()); + } + } + + HttpResponse::NotFound().json(APIError { code: "Sync ID not known".to_string(), message: "Missing sync ID in DB".to_string() }) +} diff --git a/backend/src/route_order.rs b/backend/src/route_order.rs index 9066d01..320aec7 100644 --- a/backend/src/route_order.rs +++ b/backend/src/route_order.rs @@ -1,15 +1,18 @@ use std::collections::HashMap; -use actix_web::{get, HttpResponse, post}; -use actix_web::web::{Data, Json}; +use actix_web::{get, HttpRequest, HttpResponse, post}; +use actix_web::http::header::HeaderValue; +use actix_web::web::{Data, Json, Path}; use bonsaidb::core::schema::SerializedCollection; +use log::debug; use serde::{Deserialize, Serialize}; +use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems, CreateCheckoutSessionLineItemsPriceData, CreateCheckoutSessionLineItemsPriceDataProductData, Currency, Metadata}; use crate::AppState; -use crate::db_orders::{Order, OrderType}; +use crate::db_orders::{Order, OrderType, StripeStatus}; use crate::db_products::Product; use crate::error::APIError; #[derive(Deserialize)] -pub struct CashOrderRequest { +pub struct OrderRequest { pub products: HashMap, pub adjust_stock: bool } @@ -20,7 +23,10 @@ pub struct CashOrderResponse { } #[post("/cash_order")] -pub async fn cash_order(req: Json, db: Data) -> HttpResponse { +pub async fn cash_order(req: Json, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } // load product data let mut products = match Product::get_multiple_async(req.products.keys(), &db.db).await { Ok(r) => r, @@ -42,6 +48,7 @@ pub async fn cash_order(req: Json, db: Data) -> Http order_type: OrderType::Cash, total_usd: price_total, products: req.products.clone(), + stripe_status: StripeStatus::NotStripeTransaction }; if req.adjust_stock { @@ -95,11 +102,15 @@ pub struct OrderResponse { pub id: u64, pub order_type: OrderType, pub total_usd: f64, - pub products: HashMap + pub products: HashMap, + pub stripe_status: StripeStatus } #[get("/orders")] -pub async fn get_orders(db: Data) -> HttpResponse { +pub async fn get_orders(db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let orders = match Order::list_async(0.., &db.db).await { Ok(p) => p, Err(e) => { @@ -115,7 +126,218 @@ pub async fn get_orders(db: Data) -> HttpResponse { order_type: u.contents.order_type.clone(), total_usd: u.contents.total_usd, products: u.contents.products.clone(), + stripe_status: u.contents.stripe_status.clone() }).collect(); HttpResponse::Ok().json(OrdersResponse { orders: resp }) +} + +#[get("/order/{id}")] +pub async fn get_order(id: Path, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } + let orders = match Order::get_async(id.into_inner(), &db.db).await { + Ok(p) => p, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { + code: "DB_ERROR".to_string(), + message: e.to_string(), + }) + } + }; + + if let Some(u) = orders { + HttpResponse::Ok().json(OrderResponse { + id: u.header.id, + order_type: u.contents.order_type.clone(), + total_usd: u.contents.total_usd, + products: u.contents.products.clone(), + stripe_status: u.contents.stripe_status.clone() + }) + } else { + HttpResponse::NotFound().finish() + } +} + +#[derive(Serialize)] +pub struct StripeOrderResponse { + pub order_id: u64, + pub checkout_url: String +} + +#[post("/stripe_order")] +pub async fn stripe_order(req: Json, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } + // load product data + let mut products = match Product::get_multiple_async(req.products.keys(), &db.db).await { + Ok(r) => r, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { + code: "DB_ERROR".to_string(), + message: e.to_string(), + }) + } + }; + + let mut products_map: HashMap = HashMap::new(); + for product in &products { + products_map.insert(product.header.id, Product { + name: product.contents.name.clone(), + price_usd: product.contents.price_usd, + stock: product.contents.stock, + }); + } + + let mut price_total: f64 = 0.0; + + for product in &products { + price_total += req.products[&product.header.id] as f64 * product.contents.price_usd; + } + + if req.adjust_stock { + for product in &mut products { + if product.contents.stock < req.products[&product.header.id] { + return HttpResponse::BadRequest().json(APIError { + code: "NOT_ENOUGH_STOCK".to_string(), + message: "Cannot sell more items than are in stock backend".to_string(), + }); + } + } + } + + // Calculate the stripe fee + let f_fixed = 0.30; + let f_percent = 0.029; + let p_charge = (price_total + f_fixed) / (1f64 - f_percent); + let stripe_fee = p_charge - price_total; + + debug!("Stripe transactional fee: {} for a {} total", stripe_fee, price_total); + + // Create line items + + let mut products_filtered = vec![]; + for (product_id, product_qty) in &req.products { + if *product_qty != 0 { + products_filtered.push((*product_id, *product_qty)); + } + } + + let line_items = products_filtered.iter().map(|(id, qty)| { + CreateCheckoutSessionLineItems { + adjustable_quantity: None, + dynamic_tax_rates: None, + price: None, + price_data: Some(CreateCheckoutSessionLineItemsPriceData { + currency: Currency::USD, + product: None, + product_data: Some(CreateCheckoutSessionLineItemsPriceDataProductData { + description: None, + images: None, + metadata: Metadata::from([ + ("X-LMSPOS-ID".to_string(), id.to_string()) + ]), + name: products_map[id].name.clone(), + tax_code: None, + }), + recurring: None, + tax_behavior: None, + unit_amount: Some((products_map[id].price_usd * 100f64) as i64), + unit_amount_decimal: None, + }), + quantity: Some(*qty), + tax_rates: None, + } + }).collect(); + + let doc = Order { + order_type: OrderType::Stripe, + total_usd: price_total, + products: req.products.clone(), + stripe_status: StripeStatus::SessionCreated + }; + + let order = match doc.push_into_async(&db.db).await { + Ok(r) => r, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { + code: "DB_ERROR".to_string(), + message: e.to_string(), + }) + } + }; + + let checkout_session = CheckoutSession::create(&db.client, CreateCheckoutSession { + after_expiration: None, + allow_promotion_codes: None, + automatic_tax: None, + billing_address_collection: None, + cancel_url: Some(&format!("http://192.168.254.201:8080/stripe_cancel?session_id={{CHECKOUT_SESSION_ID}}&order={}", order.header.id)), + client_reference_id: None, + consent_collection: None, + currency: None, + custom_fields: None, + custom_text: None, + customer: None, + customer_creation: None, + customer_email: None, + customer_update: None, + discounts: None, + expand: &[], + expires_at: None, + invoice_creation: None, + line_items: Some(line_items), + locale: None, + metadata: Some(Metadata::from([ + ("X-LMSPOS-Affects-Stock".to_string(), req.adjust_stock.to_string()) + ])), + mode: Some(CheckoutSessionMode::Payment), + payment_intent_data: None, + payment_method_collection: None, + payment_method_options: None, + payment_method_types: None, + phone_number_collection: None, + setup_intent_data: None, + shipping_address_collection: None, + shipping_options: None, + shipping_rates: None, + submit_type: None, + subscription_data: None, + success_url: &format!("http://192.168.254.201:8080/stripe_finish?session_id={{CHECKOUT_SESSION_ID}}&order={}", order.header.id), + tax_id_collection: None, + }); + + let session = match checkout_session.await { + Ok(r) => r, + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { + code: "STRIPE_ERR".to_string(), + message: e.to_string(), + }) + } + }; + + debug!("Payment session created! ID: {}", session.id); + + if req.adjust_stock { + for product in &mut products { + product.contents.stock -= req.products[&product.header.id]; + match product.update_async(&db.db).await { + Ok(_) => (), + Err(e) => { + return HttpResponse::InternalServerError().json(APIError { + code: "DB_ERROR".to_string(), + message: e.to_string(), + }) + } + } + } + } + + HttpResponse::Ok().json(StripeOrderResponse { + order_id: order.header.id, + checkout_url: session.url.unwrap(), + }) } \ No newline at end of file diff --git a/backend/src/route_products.rs b/backend/src/route_products.rs index 6f46074..4c76ff7 100644 --- a/backend/src/route_products.rs +++ b/backend/src/route_products.rs @@ -1,4 +1,5 @@ -use actix_web::{get, post, HttpResponse, put, delete}; +use actix_web::{get, post, HttpResponse, put, delete, HttpRequest}; +use actix_web::http::header::HeaderValue; use actix_web::web::{Data, Json, Path}; use bonsaidb::core::schema::SerializedCollection; use serde::{Deserialize, Serialize}; @@ -20,7 +21,10 @@ pub struct ProductResponse { } #[get("/products")] -pub async fn get_products(db: Data) -> HttpResponse { +pub async fn get_products(db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let products = match Product::list_async(0.., &db.db).await { Ok(p) => p, Err(e) => { @@ -37,7 +41,10 @@ pub async fn get_products(db: Data) -> HttpResponse { } #[get("/products/{id}")] -pub async fn get_product(id: Path, db: Data) -> HttpResponse { +pub async fn get_product(id: Path, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let product = match Product::get_async(id.into_inner(), &db.db).await { Ok(p) => p, Err(e) => { @@ -71,7 +78,10 @@ pub struct CreateProductRequest { } #[post("/products")] -pub async fn create_product(req: Json, db: Data) -> HttpResponse { +pub async fn create_product(req: Json, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let document = Product { name: req.name.clone(), price_usd: req.price_usd, @@ -103,7 +113,10 @@ pub struct UpdateProductRequest { } #[put("/products/{id}")] -pub async fn update_product(id: Path, req: Json, db: Data) -> HttpResponse { +pub async fn update_product(id: Path, req: Json, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let document = match Product::get_async(id.into_inner(), &db.db).await { Ok(p) => p, Err(e) => { @@ -144,7 +157,10 @@ pub async fn update_product(id: Path, req: Json, db: } #[delete("/products/{id}")] -pub async fn delete_product(id: Path, db: Data) -> HttpResponse { +pub async fn delete_product(id: Path, db: Data, req_info: HttpRequest) -> HttpResponse { + if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) { + return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() }); + } let document = match Product::get_async(id.into_inner(), &db.db).await { Ok(p) => p, Err(e) => { diff --git a/backend/src/route_stripe.rs b/backend/src/route_stripe.rs new file mode 100644 index 0000000..df4c456 --- /dev/null +++ b/backend/src/route_stripe.rs @@ -0,0 +1,135 @@ +use std::str::FromStr; +use actix_web::{get, HttpResponse}; +use actix_web::web::{Data, Path, Query}; +use bonsaidb::core::schema::SerializedCollection; +use log::error; +use serde::Deserialize; +use stripe::{CheckoutSession, CheckoutSessionId, CheckoutSessionPaymentStatus}; +use crate::AppState; +use crate::db_orders::{Order, StripeStatus}; +use crate::db_products::Product; + +#[derive(Deserialize)] +pub struct StripeCancelQuery { + session_id: String, + order: u64 +} + +#[get("/stripe_cancel")] +pub async fn stripe_cancel(req: Query, db: Data) -> HttpResponse { + // lookup order ID + let order = match Order::get_async(req.order, &db.db).await { + Ok(r) => r, + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + }; + + let order = match order { + Some(o) => o, + None => { + return HttpResponse::NotFound().body("We were unable to find your order in our systems. Please try again later.") + } + }; + + // make sure the order isnt over yet + if order.contents.stripe_status != StripeStatus::SessionCreated { + return HttpResponse::BadRequest().body("This transaction has already been finished or cancelled. Please try again later.") + } + + // cancel the order in our database + let mut order = order; + order.contents.stripe_status = StripeStatus::Failed; + match order.update_async(&db.db).await { + Ok(_) => (), + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + } + + // lookup and cancel the order in stripe - do we need to un-adjust stock? + let session = match CheckoutSession::expire(&db.client, &CheckoutSessionId::from_str(&req.session_id).unwrap()).await { + Ok(s) => s, + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + }; + + let need_unadjust_stock = session.metadata.get("X-LMSPOS-Affects-Stock") == Some(&String::from("true")); + + if need_unadjust_stock { + let mut products = match Product::get_multiple_async(order.contents.products.keys(), &db.db).await { + Ok(r) => r, + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later."); + } + }; + + for product in &mut products { + product.contents.stock += order.contents.products[&product.header.id]; + match product.update_async(&db.db).await { + Ok(_) => (), + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later."); + } + } + } + } + + HttpResponse::Ok().body("Your order has been cancelled. We hope to see you again soon!") +} + +#[get("/stripe_finish")] +pub async fn stripe_finish(req: Query, db: Data) -> HttpResponse { + // lookup order ID + let order = match Order::get_async(req.order, &db.db).await { + Ok(r) => r, + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + }; + + let order = match order { + Some(o) => o, + None => { + return HttpResponse::NotFound().body("We were unable to find your order in our systems. Please try again later.") + } + }; + + // make sure the order isnt over yet + if order.contents.stripe_status != StripeStatus::SessionCreated { + return HttpResponse::BadRequest().body("This transaction has already been finished or cancelled. Please try again later.") + } + + // lookup the order in stripe + let session = match CheckoutSession::retrieve(&db.client, &CheckoutSessionId::from_str(&req.session_id).unwrap(), &[]).await { + Ok(s) => s, + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + }; + + if session.payment_status != CheckoutSessionPaymentStatus::Paid { + return HttpResponse::BadRequest().body("This transaction has not yet been paid. Please try again later.") + } + + // finish the order in our database + let mut order = order; + order.contents.stripe_status = StripeStatus::Paid; + match order.update_async(&db.db).await { + Ok(_) => (), + Err(e) => { + error!("{}", e); + return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.") + } + } + + HttpResponse::Ok().body("Thank you for your order! Payment has been initiated and your transaction has been completed. We hope to see you again soon!") +} \ No newline at end of file diff --git a/frontend/src/lib/stores/AuthStore.ts b/frontend/src/lib/stores/AuthStore.ts new file mode 100644 index 0000000..b2c8087 --- /dev/null +++ b/frontend/src/lib/stores/AuthStore.ts @@ -0,0 +1,3 @@ +import { persist } from "$lib/PersistentStore"; + +export const authToken = persist("auth", "YOUR_TOKEN_HERE"); diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 10a590f..5f32c1d 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -11,6 +11,10 @@ import HelperText from "@smui/textfield/helper-text"; import Checkbox from "@smui/checkbox"; import FormField from "@smui/form-field"; + import CircularProgress from "@smui/circular-progress"; + import QRJS from "$lib/components/QRJS.svelte"; + import {authToken} from "$lib/stores/AuthStore"; + import {browser} from "$app/environment"; let products = []; let productsCounter = {}; @@ -28,7 +32,7 @@ async function loadProducts() { loaded = false; try { - let resp = await fetch(`${$serverUrl}/products`); + let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]}); if (resp.ok) { let json = await resp.json(); console.log(json); @@ -147,7 +151,8 @@ let resp = await fetch(`${$serverUrl}/cash_order`, { method: 'POST', headers: [ - ['Content-Type', 'application/json'] + ['Content-Type', 'application/json'], + ["Authorization", "Bearer " + $authToken] ], body: JSON.stringify({ products: productsCounter, @@ -174,6 +179,222 @@ return; } } + + let stripeOpen = false; + let stripeTransactionFinished = false; + let stripeTransactionStarted = false; + + let stripeProgressSpinner = false; + let stripeProgressSpinnerText = ""; + let stripeProgressSpinnerClosed = false; + + let stripeCanClose = false; + function updateCanClose() { + stripeCanClose = !stripeTransactionFinished; + } + $: stripeTransactionFinished, updateCanClose(); + + let hasStripeUrl = false; + + let order_url = ''; + + async function startStripeTransaction() { + stripeTransactionStarted = true; + + stripeProgressSpinner = true; + stripeProgressSpinnerText = "Contacting Stripe API to initiate transaction..."; + + let order_id = ''; + + + try { + let resp = await fetch(`${$serverUrl}/stripe_order`, { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ["Authorization", "Bearer " + $authToken] + ], + body: JSON.stringify({ + products: productsCounter, + adjust_stock: adjustStockOnOrderFinish + }) + }); + if (resp.ok) { + let json = await resp.json(); + loadedCashIn = true; + snackbarText = "Order started! Order ID: " + json.order_id; + console.log(json); + + order_url = json.checkout_url; + order_id = json.order_id; + + hasStripeUrl = true; + + snackbar.open(); + } else { + console.error(await resp.text()); + snackbarText = "There was an error starting the order. Check the browser console for more information."; + snackbar.open(); + + stripeProgressSpinnerText = "Transaction failed. Check browser console for more information."; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + return; + } + } catch (e) { + console.error(e); + snackbarText = "There was an error starting the order. Check the browser console for more information."; + snackbar.open(); + + stripeProgressSpinnerText = "Transaction failed. Check browser console for more information."; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + return; + } + + stripeProgressSpinnerText = "Waiting for customer..."; + + async function updateOrderStatus() { + console.log('polling for updated stripe order info'); + try { + let resp = await fetch(`${$serverUrl}/order/${order_id}`, { + method: 'GET', + headers: [["Authorization", "Bearer " + $authToken]] + }); + if (resp.ok) { + let json = await resp.json(); + + if (json.stripe_status === 'SessionCreated') { + console.log('result: SessionCreated waiting for customer'); + setTimeout(updateOrderStatus, 1000); + return; + } else if (json.stripe_status === 'Paid') { + hasStripeUrl = false; + console.log('result: Paid order complete'); + stripeProgressSpinnerText = 'Order complete! Payment was successful.'; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + snackbarText = "Order complete! Order ID: " + json.id; + snackbar.open(); + return; + } else { + hasStripeUrl = false; + console.log('result: Other order cancelled'); + stripeProgressSpinnerText = 'Order cancelled by customer or payment provider'; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + snackbarText = "Order cancelled! Order ID: " + json.id; + snackbar.open(); + return; + } + } else { + hasStripeUrl = false; + console.error(await resp.text()); + snackbarText = "There was an error checking the order status. Check the browser console for more information."; + snackbar.open(); + + stripeProgressSpinnerText = "Transaction failed. Check browser console for more information."; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + return; + } + } catch (e) { + hasStripeUrl = false; + console.error(e); + snackbarText = "There was an error checking the order status. Check the browser console for more information."; + snackbar.open(); + + stripeProgressSpinnerText = "Transaction failed. Check browser console for more information."; + stripeProgressSpinnerClosed = true; + stripeTransactionFinished = true; + return; + } + } + + await updateOrderStatus(); + } + + function openStripe() { + stripeTransactionFinished = false; + stripeTransactionStarted = false; + + stripeProgressSpinner = false; + stripeProgressSpinnerText = ""; + stripeProgressSpinnerClosed = false; + + stripeOpen = true; + } + + + function uuid() { + var uuid = "", i, random; + for (i = 0; i < 32; i++) { + random = Math.random() * 16 | 0; + + if (i == 8 || i == 12 || i == 16 || i == 20) { + uuid += "-" + } + uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16); + } + return uuid; + } + + let cs_view_sync_code = uuid(); + let showCsQrCode = false; + let csQrCodeUrl = ""; + + onMount(() => { + csQrCodeUrl = `${window.location}customer?cs=${cs_view_sync_code}`; + }); + + async function resendCs() { + if (!browser) return; + + let product_slice = {}; + + for (let i = 0; i < products.length; i++) { + let product = products[i]; + let counter = productsCounter[product.id]; + + product_slice[product.id] = [ + { + name: `${product.name} (#${product.id})`, + price_usd: Math.trunc(product.price_usd * 100) + }, + counter + ] + } + + try { + let resp = await fetch(`${$serverUrl}/custview`, { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ["Authorization", "Bearer " + $authToken] + ], + body: JSON.stringify({ + sync_id: cs_view_sync_code, + products: product_slice, + price_total: total + }) + }); + if (!resp.ok) { + console.error(await resp.text()); + snackbarText = "There was an error updating the customer view. Check the browser console for more information."; + snackbar.open(); + + return; + } + } catch (e) { + console.error(e); + snackbarText = "There was an error updating the customer view. Check the browser console for more information."; + snackbar.open(); + + return; + } + } + + $: productsCounter, resendCs();
@@ -234,7 +455,7 @@ - + + + Customer View QR Code + + Use this QR code on another device to open the Customer View. + + + + Cash In @@ -293,4 +527,35 @@ Cancel + + + + Stripe Transaction + +

Total Price: {formatter.format(total)}

+

Stripe transactions cannot be stopped once started. Confirm with customer now before starting the transaction.

+ + {#if hasStripeUrl} + + + {/if} + {#if stripeProgressSpinner} +
+ + {stripeProgressSpinnerText} +
+ {/if} +
+ + + +
\ No newline at end of file diff --git a/frontend/src/routes/customer/+page.svelte b/frontend/src/routes/customer/+page.svelte new file mode 100644 index 0000000..19a980c --- /dev/null +++ b/frontend/src/routes/customer/+page.svelte @@ -0,0 +1,96 @@ + + + + {snackbarText} + + + + + + Name + Price Ea + Qty + Price + + + + {#each data as [num, data]} + {#if data[1] !== 0} + + {data[0].name} + {formatter.format(data[0].price_usd / 100)} + {data[1]} + {formatter.format(data[0].price_usd * data[1] / 100)} + + {/if} + {/each} + + + + Total + + {formatter.format(total)} + + + + + + \ No newline at end of file diff --git a/frontend/src/routes/manage/env/+page.svelte b/frontend/src/routes/manage/env/+page.svelte index eeef61d..bdc71e8 100644 --- a/frontend/src/routes/manage/env/+page.svelte +++ b/frontend/src/routes/manage/env/+page.svelte @@ -8,6 +8,7 @@ import {serverUrl} from "$lib/stores/ServerStore"; import Snackbar from "@smui/snackbar"; + import {authToken} from "$lib/stores/AuthStore"; let snackbarTesting: Snackbar; let snackbarTestSuccess: Snackbar; @@ -36,6 +37,10 @@ snackbarTestFailure.open(); return; } + } else { + authTokenTesting.close(); + authTokenTestFailure.open(); + return; } } catch (e) { console.error(e); @@ -44,6 +49,46 @@ return; } } + + let authTokenTesting: Snackbar; + let authTokenTestSuccess: Snackbar; + let authTokenTestFailure: Snackbar; + let changeAuthTokenOpen = false; + + let changeAuthTokenFocused; + let changeAuthTokenDirty; + let newAuthToken = $authToken; + let changeAuthTokenInvalid; + + async function testAuthTokenConnection() { + authTokenTesting.open(); + try { + let resp = await fetch(`${$serverUrl}/auth_status`, {headers: [["Authorization", "Bearer " + $authToken]]}); + if (resp.ok) { + let json = await resp.json(); + if (json.status === "ok") { + // ok! + authTokenTesting.close(); + authTokenTestSuccess.open(); + return; + } else { + console.error("Response .status was not ok"); + authTokenTesting.close(); + authTokenTestFailure.open(); + return; + } + } else { + authTokenTesting.close(); + authTokenTestFailure.open(); + return; + } + } catch (e) { + console.error(e); + authTokenTesting.close(); + authTokenTestFailure.open(); + return; + } + }
@@ -62,6 +107,20 @@ + + Authentication Token + Configure the token that LMSPOS uses to authenticate with the backend. + +

Current:

{$authToken}
+ + +
+
+ @@ -72,6 +131,16 @@ + + + + + + + + + + Change server URL @@ -90,4 +159,20 @@ + + + + Change auth token + + This is the token that LMSPOS will use to connect to it's backend. It is highly recommended that you use the "Test" button to ensure this is working properly before proceeding.
+ {changeAuthTokenFocused = true}} on:blur={() => {changeAuthTokenFocused = false}}> +
+ + + +
\ No newline at end of file diff --git a/frontend/src/routes/manage/orders/+page.svelte b/frontend/src/routes/manage/orders/+page.svelte index 9d423b6..ff487a8 100644 --- a/frontend/src/routes/manage/orders/+page.svelte +++ b/frontend/src/routes/manage/orders/+page.svelte @@ -6,6 +6,7 @@ import { onMount } from 'svelte'; import {serverUrl} from "$lib/stores/ServerStore"; import Paper, { Title as PaperTitle, Content as PaperContent } from "@smui/paper"; + import {authToken} from "$lib/stores/AuthStore"; let loaded = false; let orders = []; @@ -18,7 +19,7 @@ async function loadOrders() { loaded = false; try { - let resp = await fetch(`${$serverUrl}/orders`); + let resp = await fetch(`${$serverUrl}/orders`, {headers: [["Authorization", "Bearer " + $authToken]]}); if (resp.ok) { let json = await resp.json(); console.log(json); @@ -45,7 +46,7 @@ async function loadProducts() { loaded = false; try { - let resp = await fetch(`${$serverUrl}/products`); + let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]}); if (resp.ok) { let json = await resp.json(); console.log(json); @@ -108,6 +109,10 @@ let stripe_orders = 0; let cash_orders = 0; + let stripe_sessioncreated = 0; + let stripe_failed = 0; + let stripe_paid = 0; + function updateStats() { for (let i = 0; i < orders.length; i++) { if (orders[i].order_type === "Cash") { @@ -115,6 +120,13 @@ } if (orders[i].order_type === "Stripe") { stripe_orders += 1; + if (orders[i].stripe_status === 'SessionCreated') { + stripe_sessioncreated += 1; + } else if (orders[i].stripe_status === 'Failed') { + stripe_failed += 1; + } else if (orders[i].stripe_status === 'Paid') { + stripe_paid += 1; + } } } } @@ -131,6 +143,7 @@ ID Products Order Type + Stripe Status Revenue @@ -142,6 +155,7 @@ {buildProductsString(order)} {order.order_type} + {order.stripe_status} {formatter.format(order.total_usd)} {/each} @@ -149,6 +163,7 @@ + Total Revenue {formatter.format(total.toFixed(2))} @@ -165,6 +180,9 @@

order_type = STRIPE: {stripe_orders}

Percentage of orders using CASH: {(cash_orders / orders.length) * 100}%

Percentage of orders using STRIPE: {(stripe_orders / orders.length) * 100}%

+

Percentage of STRIPE, stripe_status = SessionCreated: {stripe_sessioncreated} ({stripe_sessioncreated / stripe_orders * 100}%)

+

Percentage of STRIPE, stripe_status = Failed: {stripe_failed} ({stripe_failed / stripe_orders * 100}%)

+

Percentage of STRIPE, stripe_status = Paid: {stripe_paid} ({stripe_paid / stripe_orders * 100}%)

diff --git a/frontend/src/routes/manage/products/+page.svelte b/frontend/src/routes/manage/products/+page.svelte index e529cd2..8647b76 100644 --- a/frontend/src/routes/manage/products/+page.svelte +++ b/frontend/src/routes/manage/products/+page.svelte @@ -14,6 +14,7 @@ import Tooltip, { Wrapper as TooltipWrapper } from "@smui/tooltip"; import "./style.scss"; + import {authToken} from "$lib/stores/AuthStore"; let loaded = false; let products = []; @@ -26,7 +27,7 @@ async function loadProducts() { loaded = false; try { - let resp = await fetch(`${$serverUrl}/products`); + let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]}); if (resp.ok) { let json = await resp.json(); console.log(json); @@ -86,7 +87,8 @@ let resp = await fetch(`${$serverUrl}/products/${editingProductID}`, { method: 'PUT', headers: [ - ['Content-Type', 'application/json'] + ['Content-Type', 'application/json'], + ["Authorization", "Bearer " + $authToken] ], body: JSON.stringify({ name: editName, @@ -126,7 +128,7 @@ deletingProduct = false; loaded = false; try { - let resp = await fetch(`${$serverUrl}/products/${id}`, {method: 'DELETE'}); + let resp = await fetch(`${$serverUrl}/products/${id}`, {method: 'DELETE', headers: [["Authorization", "Bearer " + $authToken]]}); if (resp.ok) { loaded = true; await loadProducts(); @@ -177,7 +179,8 @@ let resp = await fetch(`${$serverUrl}/products`, { method: 'POST', headers: [ - ['Content-Type', 'application/json'] + ['Content-Type', 'application/json'], + ["Authorization", "Bearer " + $authToken] ], body: JSON.stringify({ name: createName,