From f6797049af0c31c7af61d0ed0a2c63aa8b74a99f Mon Sep 17 00:00:00 2001 From: willem640 Date: Sat, 22 Apr 2023 22:30:58 +0000 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 1640 +++++++++++++++++++++++++++++++++++ Cargo.toml | 17 + TODO | 11 + assets/hello.html | 11 + assets/white_pixel.png | Bin 0 -> 161 bytes src/db.rs | 101 +++ src/main.rs | 201 +++++ src/pixel.rs | 482 ++++++++++ templates/base.html | 13 + templates/index.html | 32 + templates/manage/pixel.html | 39 + templates/navbar.html | 10 + 13 files changed, 2558 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 TODO create mode 100644 assets/hello.html create mode 100644 assets/white_pixel.png create mode 100644 src/db.rs create mode 100644 src/main.rs create mode 100644 src/pixel.rs create mode 100644 templates/base.html create mode 100644 templates/index.html create mode 100644 templates/manage/pixel.html create mode 100644 templates/navbar.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9bd19d0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1640 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[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 = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "askama" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94" +dependencies = [ + "basic-toml", + "mime", + "mime_guess", + "nom", + "proc-macro2", + "quote", + "serde", + "syn 2.0.13", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "basic-toml" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "commoncrypto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007" +dependencies = [ + "commoncrypto-sys", +] + +[[package]] +name = "commoncrypto-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2" +dependencies = [ + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-hash" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a77162240fd97248d19a564a565eb563a3f592b386e4136fb300909e67dddca" +dependencies = [ + "commoncrypto", + "hex", + "openssl", + "winapi", +] + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.13", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", + "gzip-header", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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 = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gzip-header" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +dependencies = [ + "crc32fast", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "pin-utils", + "scoped-tls", + "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]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "oneshot" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc22d22931513428ea6cc089e942d38600e3d00976eef8c86de6b8a3aadec6eb" +dependencies = [ + "loom", +] + +[[package]] +name = "openssl" +version = "0.10.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +dependencies = [ + "bitflags 1.3.2", + "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.13", +] + +[[package]] +name = "openssl-sys" +version = "0.9.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rouille" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f86e4c51a773f953f02bbab5fd049f004bfd384341d62da2a079aff812ab176" +dependencies = [ + "base64 0.13.1", + "brotli", + "chrono", + "deflate", + "filetime", + "multipart", + "num_cpus", + "percent-encoding", + "rand", + "serde", + "serde_derive", + "serde_json", + "sha1", + "threadpool", + "time 0.3.20", + "tiny_http", + "url", +] + +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.1.0", + "chrono", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustix" +version = "0.37.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +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 = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "libc", + "num_threads", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracking_pixel" +version = "0.1.0" +dependencies = [ + "askama", + "base64 0.21.0", + "chrono", + "crypto-hash", + "oneshot", + "rand", + "rouille", + "rusqlite", + "thiserror", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[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" +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 = "wasi" +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 = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8678868 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tracking_pixel" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +askama = "0.12.0" +base64 = "0.21.0" +chrono = "0.4.24" +crypto-hash = "0.3.4" +oneshot = "0.1.5" +rand = "0.8.5" +rouille="3.6.1" +rusqlite = { version = "0.29.0", features = ["bundled", "chrono"] } +thiserror = "1.0.40" diff --git a/TODO b/TODO new file mode 100644 index 0000000..59c20e5 --- /dev/null +++ b/TODO @@ -0,0 +1,11 @@ + - implement limit on number of shown hits + - date on pixel + - fix redirect + - pragma foreign_keys + - session + - ip blacklist + - clear/delete hits + - move db operations out of db_delegate + - ORM? + - async? + - pipelining? \ No newline at end of file diff --git a/assets/hello.html b/assets/hello.html new file mode 100644 index 0000000..819bca9 --- /dev/null +++ b/assets/hello.html @@ -0,0 +1,11 @@ + + + + Hello + + +

+ Hallo +

+ + \ No newline at end of file diff --git a/assets/white_pixel.png b/assets/white_pixel.png new file mode 100644 index 0000000000000000000000000000000000000000..982323ab79fa055bc7777e027e5b419bf7644418 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMwQX@Rme0>?T zfNTy1#`a7G7LXDkmI7i12Id8f49q~95hS*N2` { + HangUp, + Message(T) +} + +#[derive(Debug)] +pub struct DBSender { + sender: Sender> +} + +// #[derive(Clone)] requires MsgT to be Clone, but that's not necessary +impl Clone for DBSender { + fn clone(&self) -> Self { + Self {sender: self.sender.clone()} + } +} + +impl DBSender { + pub fn send(&self, msg: MsgT) -> Result<(), SendError>{ + if let Err(err) = self.sender.send(DBMessage::Message(msg)) { + if let DBMessage::Message(msg) = err.0 { + Err(SendError(msg)) + } else { + unreachable!(); + } + } else { + Ok(()) + } + } + + fn hang_up(&self) -> Result<(), SendError<()>> { + if let Err(_) = self.sender.send(DBMessage::HangUp) { + Err(SendError(())) + } + else { + Ok(()) + } + } +} + + +#[derive(Debug)] +pub struct DBConnection +{ + pub sender: DBSender, + join_handle: Option> +} + + +impl DBConnection + { + pub fn init(connection: Connection, op: F) -> Self { + let (sender, receiver) = channel(); + + let join_handle = thread::spawn(move || Self::db_thread(receiver, connection, op)); + let sender = DBSender { sender }; + + Self {sender, join_handle: Some(join_handle)} + } + + fn db_thread(receiver: Receiver>, connection: Connection, op: F) { + while let Ok(db_msg) = receiver.recv() { + // _ = connection.execute("INSERT INTO hits VALUES(?1, ?2, ?3, ?4);", params![hit.pixel_id, hit.ip, hit.user_agent, hit.date]).unwrap(); + if let DBMessage::Message(msg) = db_msg { + op(msg, &connection); + } + else if let DBMessage::HangUp = db_msg { + break; + } + } + + // execute last messages in buffer + for db_msg in receiver.try_iter() { + if let DBMessage::Message(msg) = db_msg { + op(msg, &connection); + } + } + } + // static is implied, but included for clearness + pub fn finish(&mut self) -> Result<(), Box> { + // let Self{sender, join_handle} = self; + if let Err(err) = self.sender.hang_up() { + return Err(Box::new(err)); + } + if let Some(handle) = self.join_handle.take() { + handle.join() + } + else { + Ok(()) + } + } +} + +impl Drop for DBConnection { + fn drop(&mut self) { + self.finish().unwrap(); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4c1cf64 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,201 @@ +use std::{fs, sync::{Mutex}}; + +use askama::Template; +use rouille::{Response, router, input, Request, try_or_400, post_input}; + + +use pixel::{Hit, PixelManager, PixelManagerDelegate, TrackingPixel, User}; + +mod db; +mod pixel; + + +fn main() { + let white_1x1_pixel_png = fs::read("assets/white_pixel.png").unwrap(); + + let pixel_manager = PixelManager::build("app.db").unwrap(); + + let pixel_manager_delegate = PixelManagerDelegate::new(&pixel_manager); + // let pixel_id = pixel_manager.create_pixel("Hallo mac").unwrap(); + // print!("{pixel_id}"); + // pixel_manager.register_hit(pixel_id, "ai pie", "joeser eedsjent", chrono::offset::Utc::now()); + + let delegate_mutex = Mutex::new(pixel_manager_delegate); + + rouille::start_server("0.0.0.0:3737", move |request| { + router!(request, + (GET) ["/manage"] => { + let delegate = delegate_mutex.lock().unwrap().clone(); + // panicking on poisoned mutex is acceptable + manage_index(request, &delegate) + }, + (GET) ["/manage/pixel/{pixel_id}", pixel_id: String] => { + let delegate = delegate_mutex.lock().unwrap().clone(); + manage_pixel(&pixel_id, request, &delegate) + }, + (POST) ["/manage/pixel/create"] => { + let delegate = delegate_mutex.lock().unwrap().clone(); + create_pixel(request, &delegate) + }, + (POST) ["/manage/pixel/delete"] => { + let delegate = delegate_mutex.lock().unwrap().clone(); + delete_pixel(request, &delegate) + }, + (GET) ["/img/{id}/white.png", id: String] => { + let delegate = delegate_mutex.lock().unwrap().clone(); + register_hit(&id, request, &delegate); + Response::from_data("image/png", white_1x1_pixel_png.clone()) + }, + _ => { + println!("404: {:#?}", request.url()); + Response::empty_404() + } + ) + }); + +} + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate<'a> { + username: &'a str, + pixels: Vec<(String, String)> +} + +impl<'a> IndexTemplate<'a> { + fn new(username: &'a str, pixels: &Vec) -> Self { + Self { + username, + pixels: pixels.iter().map(|pix| (pix.name.clone(), pix.url_safe_encode())).collect() + } + } +} + + +#[derive(Template)] +#[template(path = "manage/pixel.html")] +struct PixelTemplate { + pixel_name: String, + hits: Vec<(String, String, String)>, + pixel_url: String, + pixel_id: String +} + +impl PixelTemplate { + fn new(pixel: &TrackingPixel, hits: &Vec) -> Self { + Self { + pixel_name: pixel.name.clone(), + hits: hits.iter().map(|hit| (hit.date.to_string(), hit.ip.clone(), hit.user_agent.clone())).collect(), + pixel_url: get_url_for_pixel(&pixel), + pixel_id: pixel.url_safe_encode() + } + } +} + +fn manage_index(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response { + let user = match auth_user(request, &manager_delegate) { + Ok(user) => user, + Err(response) => return response + }; + + match manager_delegate.get_manager().get_pixels_for_user(&user) { + Ok(pixels) => { + let index_template = IndexTemplate::new(&user.username, &pixels); + match index_template.render() { + Ok(page) => Response::html(page), + Err(_) => Response::text("Page error").with_status_code(500) + } + }, + Err(_) => Response::text("DB error").with_status_code(500) + } +} + +fn auth_user(request: &Request, manager_delegate: &PixelManagerDelegate) -> Result { + let auth = match input::basic_http_auth(request) { + Some(a) => a, + None => return Err(Response::basic_http_auth_login_required("manage")) + }; + match manager_delegate.get_manager().get_user(&auth.login, &auth.password) { + Ok(user) => Ok(user), + Err(_) => return Err(Response::basic_http_auth_login_required("manage")) + } +} + +fn manage_pixel(pixel_id: &str, request: &Request, manager_delegate: &PixelManagerDelegate) -> Response { + let user = match auth_user(request, &manager_delegate) { + Ok(user) => user, + Err(response) => return response + }; + + let pixel = match manager_delegate.get_manager().get_pixel_from_encoded(pixel_id, Some(&user)) { + Ok(pixel_opt) => { + match pixel_opt { + Some(pixel) => pixel, + None => return Response::text("Unknown pixel").with_status_code(500) + } + }, + Err(_) => return Response::text("Encoding error").with_status_code(500) + }; + + let hits = match manager_delegate.get_manager().get_hits_for_pixel(&pixel, Some(&user)) { + Ok(hits) => hits, + Err(_) => return Response::text("DB error").with_status_code(500) + }; + + let pixel_template = PixelTemplate::new(&pixel, &hits); + + match pixel_template.render() { + Ok(page) => Response::html(page), + Err(_) => Response::text("Page error").with_status_code(500) + } +} + +fn create_pixel(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response { + let user = match auth_user(request, &manager_delegate) { + Ok(user) => user, + Err(response) => return response + }; + let input = try_or_400!(post_input!(request, { + pixel_name: String + })); + let _pixel = match manager_delegate.get_manager().create_pixel(&input.pixel_name, &user) { + Ok(pixel) => pixel, + Err(_) => return Response::empty_400() + }; + Response::redirect_303("/manage") +} + +fn delete_pixel(request: &Request, manager_delegate: &PixelManagerDelegate) -> Response { + let user = match auth_user(request, &manager_delegate) { + Ok(user) => user, + Err(response) => return response, + }; + + let input = try_or_400!(post_input!(request, { + pixel_id: String + })); + + let pixel = match manager_delegate.get_manager().get_pixel_from_encoded(&input.pixel_id, Some(&user)) { + Ok(pixel) => pixel, + Err(_) => return Response::text("DB error").with_status_code(500) + }; + if let Some(pixel) = pixel { + if manager_delegate.get_manager().delete_pixel(pixel, Some(&user)).is_err() { + return Response::text("DB error").with_status_code(500); + } + } else { + return Response::empty_400(); // could not find pixel + } + Response::redirect_303("/manage") +} + +fn register_hit(pixel_id: &str, request: &Request, manager_delegate: &PixelManagerDelegate) { + let ua = request.header("User-Agent").unwrap_or_default(); + let ip = request.header("X-Forwarded-For").unwrap_or_default(); + _ = manager_delegate.get_manager().register_hit_with_encoded_pixel(pixel_id, &ip, ua, chrono::offset::Utc::now()); +} + + +fn get_url_for_pixel(pixel: &TrackingPixel) -> String { + format!("https://track.willem.page/img/{}/white.png", pixel.url_safe_encode()) +} diff --git a/src/pixel.rs b/src/pixel.rs new file mode 100644 index 0000000..d234dcd --- /dev/null +++ b/src/pixel.rs @@ -0,0 +1,482 @@ +use std::sync::mpsc; + +use crate::db::{DBConnection, DBSender}; + +use base64::Engine; +use chrono::{DateTime, Utc}; +use crypto_hash::{Algorithm, hex_digest}; +use rusqlite::{params, Connection, OptionalExtension}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Username or password does not exist in database")] + InvalidUserData(), + #[error("Database did not respond")] + RecvError(#[from] oneshot::RecvError), + #[error("Could not send data to database")] + SendError(#[from] mpsc::SendError), + #[error("Timeout receiving data from database")] + TimeoutError(#[from] oneshot::RecvTimeoutError), + #[error("User is not authenticated")] + UserNotAuthed(), + #[error("No user with this username and password")] + InvalidCredentials(), + #[error("Data was not added to database correctly")] + DBError(), + #[error("The username for this user already exists")] + NewUserAlreadyExists(), + #[error("Invalid encoding in ID")] + InvalidEncodedID(#[from] base64::DecodeError), + #[error("Pixel not found in users pixels")] + PixelNotFound() +} + +#[derive(Debug)] +enum DBConnectionMessage { + RegisterHit(Hit), + CreatePixel(TrackingPixel), + DeletePixel(i64), + AuthUser(User, oneshot::Sender), + GetUserFromName(String, oneshot::Sender>), + GetUserFromId(i64, oneshot::Sender>), + CreateUser(User), + DeleteUser(i64), + GetHitsForPixel(TrackingPixel, oneshot::Sender>>, Option), + GetPixelsForUser(User, oneshot::Sender>>), + GetPixel(i64, Option, oneshot::Sender>) +} // messy, there is probably a better way to do this but it works so oh well + +#[derive(Debug, Clone)] +pub struct TrackingPixel { + id: i64, // ID may not be omitted when creating new pixel + pub name: String, + user_id: i64 +} + +impl TrackingPixel { + pub fn url_safe_encode(&self) -> String { + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.id.to_be_bytes()) + } + + fn url_safe_decode_id(encoded_id: &str) -> Result { + let bytes_vec = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(encoded_id)?; + let mut bytes_arr = [0; 8]; + + for i in 0..8 { + bytes_arr[i] = bytes_vec.get(i).cloned().unwrap_or_default(); + } + + Ok(i64::from_be_bytes(bytes_arr)) + } +} + +#[derive(Debug, Clone)] +pub struct Hit { + id: Option, // ID is omitted when creating new hit + pixel_id: i64, + pub ip: String, + pub user_agent: String, + pub date: DateTime +} + +#[derive(Debug, Clone, PartialEq)] +pub struct User { + id: Option, // ID is omitted when creating new user + pub username: String, + pass_hash: String, +} + + +#[derive(Debug)] +pub struct PixelManager { + db_connection_sender: DBSender, + db_connection: Option> // only original manager holds the connection, clones in delegate do not + } + +impl PixelManager { + pub fn build(db_file: &str) -> Option { + let conn = Connection::open(db_file); + if conn.is_err() { + return None; + } + let db_connection = DBConnection::init(conn.unwrap(), Self::db_delegate); + + Some(Self{ db_connection_sender: db_connection.sender.clone(), db_connection: Some(db_connection) }) + } + + fn db_delegate(msg: DBConnectionMessage, conn: &Connection) { + _ = match msg { // beware, code folding recommended + DBConnectionMessage::CreatePixel(pixel) => + conn.execute("INSERT INTO pixels (id, name, user_id) VALUES (?1, ?2, ?3)", params![pixel.id, pixel.name, pixel.user_id]), + DBConnectionMessage::RegisterHit(hit) => + conn.execute("INSERT INTO hits (pixel_id, ip, user_agent, date) VALUES (?1, ?2, ?3, ?4)", params![hit.pixel_id, hit.ip, hit.user_agent, hit.date]), + DBConnectionMessage::DeletePixel(pixel_id) => + conn.execute("DELETE FROM pixels WHERE id = ?1", [pixel_id]), + DBConnectionMessage::AuthUser(user, response_sender) => { + let result: Result; + if let User{ id: Some(id), username, pass_hash} = user { + result = conn.query_row("SELECT id FROM users WHERE id = ?1 AND username = ?2 AND pass_hash = ?3", params![id, username, pass_hash], + |row| row.get(0)); + match result.optional() { + Ok(id) => { + if let Some(_) = id { + _ = response_sender.send(true); + } else { + _ = response_sender.send(false); + } + Ok(1) // 1 row affected + } + Err(e) => {Err(e)}, + } + } else { // invalid user object + Ok(0) // 0 rows affected + } + }, + DBConnectionMessage::GetUserFromName(username, response_sender) => { + let result = conn.query_row("SELECT id, username, pass_hash FROM users WHERE username = ?1", [username], + |row| Ok( + User{ + id: row.get(0)?, + username: row.get(1)?, + pass_hash: row.get(2)? + })); + match result.optional() { + Ok(user) => { + if let Some(user) = user { + _ = response_sender.send(Some(user)); + Ok(1) // 1 row affected + } + else { + _ = response_sender.send(None); + Ok(0) // no rows affected + } + }, + Err(e) => { + _ = response_sender.send(None); + Err(e) + } + } + }, + DBConnectionMessage::GetUserFromId(id, response_sender) => { + let result = conn.query_row("SELECT id, username, pass_hash FROM users WHERE id = ?1", [id], + |row| Ok( + User{ + id: row.get(0)?, + username: row.get(1)?, + pass_hash: row.get(2)? + })); + match result.optional() { + Ok(user) => { + if let Some(user) = user { + _ = response_sender.send(Some(user)); + Ok(1) // 1 row affected + } + else { + _ = response_sender.send(None); + Ok(0) // no rows affected + } + }, + Err(e) => { + _ = response_sender.send(None); + Err(e) + } + } + }, + DBConnectionMessage::CreateUser(user) => + conn.execute("INSERT INTO users (username, pass_hash) VALUES (?1, ?2)", params![user.username, user.pass_hash]), + DBConnectionMessage::DeleteUser(id) => + conn.execute("DELETE from users WHERE id = ?1", [id]), + DBConnectionMessage::GetHitsForPixel(pixel, response_sender, user) => 'gethitsforpixel: { + let mut stmt = conn.prepare("SELECT id, pixel_id, ip, user_agent, date FROM hits WHERE pixel_id = ?1").unwrap(); + if let Some(user) = user { + if let Some(user_id) = user.id { + // check if pixel belongs to user + let pixel_id: Result = conn.query_row("SELECT id FROM pixels WHERE id = ?1 AND user_id = ?2", [pixel.id, user_id], |row| { + Ok(row.get(0)?) + }); + if !pixel_id.is_ok() { + _ = response_sender.send(None); + break 'gethitsforpixel Ok(0); + } + } + } + // if this fails something is very wrong with the underlying connection, so panicking the thread is perfectly acceptable + let mapped_rows = stmt.query_map([pixel.id], |row| { + Ok(Hit { + id: Some(row.get(0)?), + pixel_id: row.get(1)?, + ip: row.get(2)?, + user_agent: row.get(3)?, + date: row.get(4)?, + }) + }); // processing on the DB thread is a bad idea, but this really isn't very complex and I don't really care + + match mapped_rows { + Ok(hits) => { + let hits: Vec = hits.filter_map(|hit| hit.ok()).collect(); + let rows_affected = hits.len(); + _ = response_sender.send(Some(hits)); + Ok(rows_affected) + }, + Err(e) => { + _ = response_sender.send(None); + Err(e) + } + } + }, + DBConnectionMessage::GetPixelsForUser(user, response_sender) => { + let mut stmt = conn.prepare("SELECT id,name,user_id FROM pixels WHERE user_id = ?1").unwrap(); // should't fail but if it does connection is bad and panicking is acceptable + let mapped_rows = stmt.query_map([user.id], |row| { + Ok(TrackingPixel { + id: row.get(0)?, + name: row.get(1)?, + user_id: row.get(2)? + }) + }); + + match mapped_rows { + Ok(pixels) => { + let pixels: Vec = pixels.filter_map(|pixel| pixel.ok()).collect(); + let rows_affected = pixels.len(); + _ = response_sender.send(Some(pixels)); + Ok(rows_affected) + }, + Err(e) => { + _ = response_sender.send(None); + Err(e) + } + } + + }, + DBConnectionMessage::GetPixel(id, user, response_sender) => { + let mut stmt = conn.prepare("SELECT id, name, user_id FROM pixels WHERE id = ?1").unwrap(); + if let Some(user) = user { + if let Some(user_id) = user.id { + let sql_str = format!("SELECT id,name,user_id FROM pixels WHERE id = ?1 AND user_id = {}", user_id); + stmt = conn.prepare(&sql_str).unwrap(); + } + } + let pixel = stmt.query_row([id], |row| { + Ok(TrackingPixel { + id: row.get(0)?, + name: row.get(1)?, + user_id: row.get(2)? + }) + }).optional(); + match pixel { + Ok(pixel) => { + _ = response_sender.send(pixel); + Ok(1) // 1 row affected + }, + Err(e) => { + _ = response_sender.send(None); + Err(e) + } + } + } + }; + } + + pub fn create_pixel(&self, name: &str, user: &User) -> Result { + let id = rand::random::().abs(); + // yes I don't check for duplicates. but whatever + if user.id.is_none() { + return Err(Error::UserNotAuthed()); + } + + let pixel = TrackingPixel{id, name: name.to_string(), user_id: user.id.unwrap() }; + self.db_connection_sender.send(DBConnectionMessage::CreatePixel(pixel))?; + + Ok(TrackingPixel { id, name: name.to_string(), user_id: user.id.unwrap() }) + } + + pub fn delete_pixel(&self, pixel: TrackingPixel, user: Option<&User>) -> Result<(), Error> { + if let Some(user) = user { + if let Ok(pixel) = self.get_pixel_from_id(pixel.id, Some(user)) { + if pixel.is_none() { // pixel is not withing user's pixels + return Err(Error::PixelNotFound()); + } + } + } + Ok(self.db_connection_sender.send(DBConnectionMessage::DeletePixel(pixel.id))?) + } + + pub fn register_hit_with_encoded_pixel(&self, encoded_pixel: &str, ip: &str, user_agent: &str, date: DateTime) -> Result<(), Error> { + self.register_hit_with_pixel_id(TrackingPixel::url_safe_decode_id(encoded_pixel)?, ip, user_agent, date) + } + + + pub fn register_hit_with_pixel(&self, pixel: &TrackingPixel, ip: &str, user_agent: &str, date: DateTime) -> Result<(), Error> { + self.register_hit_with_pixel_id(pixel.id, ip, user_agent, date) + } + + fn register_hit_with_pixel_id(&self, id: i64, ip: &str, user_agent: &str, date: DateTime) -> Result<(), Error> { + Ok(self.db_connection_sender.send( + DBConnectionMessage::RegisterHit( + Hit { + pixel_id: id, + ip: ip.to_string(), + user_agent: user_agent.to_string(), + date, id: None + }) + )? + ) + } + + fn pass_hash(password: &str) -> String { + hex_digest(Algorithm::SHA256, password.as_bytes()) + } + + fn auth_user(&self, user: &User, password: &str) -> Result { + let (sender, receiver) = oneshot::channel(); + + + let auth_user = User { + pass_hash: Self::pass_hash(password), + ..user.clone() + }; + + self.db_connection_sender.send(DBConnectionMessage::AuthUser(auth_user, sender))?; + + let auth = receiver.recv()?; + + Ok(auth) + } + + fn get_user_from_username(&self, username: &str) -> Result, Error> { + let (sender, receiver) = oneshot::channel(); + + self.db_connection_sender.send( + DBConnectionMessage::GetUserFromName(username.to_string(), sender))?; + + Ok(receiver.recv()?) + } + + fn get_user_from_id(&self, id: i64) -> Result, Error> { + let (sender, receiver) = oneshot::channel(); + + self.db_connection_sender.send(DBConnectionMessage::GetUserFromId(id, sender))?; + + Ok(receiver.recv()?) + } + + pub fn create_user(&self, username: &str, password: &str) -> Result{ + let user = self.get_user_from_username(username)?; + if user.is_some() { + return Err(Error::NewUserAlreadyExists()); + } + + let new_user = User { + username: username.to_string(), + pass_hash: Self::pass_hash(password), + id: None + }; + self.db_connection_sender.send( + DBConnectionMessage::CreateUser(new_user))?; + + let user = self.get_user_from_username(username)?; + // get user struct that has the id + + if let Some(user) = user { + Ok(user) + } else { + Err(Error::DBError()) + } + } + + pub fn delete_user(&self, user: User) -> Result<(), Error> { + let mut id = user.id; + if id.is_none() { + let db_user = self.get_user_from_username(&user.username)?; + + if let Some(db_user) = db_user { + id = db_user.id; // must always be Some because get_user_from_username will always contain an id + } else { + return Err(Error::InvalidUserData()) + } + } + + self.db_connection_sender.send(DBConnectionMessage::DeleteUser(id.unwrap()))?; + + Ok(()) + } + + pub fn get_user(&self, username: &str, password: &str) -> Result { + let user = self.get_user_from_username(username)?; + if let Some(user) = user { + let auth = self.auth_user(&user, password)?; + if auth { + Ok(user) + } else { + Err(Error::InvalidCredentials()) // wrong password + } + } else { + Err(Error::InvalidCredentials()) // username does not exist + } + } + // optionally include a user + pub fn get_hits_for_pixel(&self, pixel: &TrackingPixel, user: Option<&User>) -> Result, Error> { + let (sender, receiver) = oneshot::channel(); + self.db_connection_sender.send(DBConnectionMessage::GetHitsForPixel(pixel.clone(), sender, user.cloned()))?; + + let hits = receiver.recv()? ; + + Ok(hits.unwrap_or_default()) + } + + pub fn get_pixels_for_user(&self, user: &User) -> Result, Error> { + let (sender, receiver) = oneshot::channel(); + + self.db_connection_sender.send(DBConnectionMessage::GetPixelsForUser(user.clone(), sender))?; + + let pixels = receiver.recv()?; + + + Ok(pixels.unwrap_or_default()) + } + + pub fn get_pixel_from_encoded(&self, encoded_id: &str, user: Option<&User>) -> Result, Error> { + let id = TrackingPixel::url_safe_decode_id(encoded_id)?; + + self.get_pixel_from_id(id, user) + } + fn get_pixel_from_id(&self, pixel_id: i64, user: Option<&User>) -> Result, Error> { + let (sender, receiver) = oneshot::channel(); + + self.db_connection_sender.send(DBConnectionMessage::GetPixel(pixel_id, user.cloned(), sender))?; + + Ok(receiver.recv()?) + } + +} + +// stores a version of the pixel manager struct with only a sender, which may be cloned and used in multiple threads +#[derive(Debug)] +pub struct PixelManagerDelegate { + manager: PixelManager +} + +impl PixelManagerDelegate { + pub fn new(pixel_manager: &PixelManager) -> Self { + let internal_pixel_manager = PixelManager { + db_connection_sender: pixel_manager.db_connection_sender.clone(), + db_connection: None + }; + Self {manager: internal_pixel_manager} + } + + pub fn get_manager(&self) -> &PixelManager { + &self.manager + } +} + +impl Clone for PixelManagerDelegate { + fn clone(&self) -> Self { + let internal_pixel_manager = PixelManager { + db_connection_sender: self.manager.db_connection_sender.clone(), + db_connection: None + }; + Self {manager: internal_pixel_manager} + } +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..f209c58 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,13 @@ + + + {% block title %}{% endblock %} + + + + {% block head %}{% endblock %} + + + + {% block body %}{% endblock %} + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..524f81f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + + +{% block body %} + +{% include "navbar.html" %} + +

Pixels for {{ username }}

+
+
+ + +
+ +
+ + + + + + + + + {% for (name, url, ) in pixels %} + + + + + {% endfor %} + +
NameDetails
{{ name|escape }}View details
+{% endblock %} \ No newline at end of file diff --git a/templates/manage/pixel.html b/templates/manage/pixel.html new file mode 100644 index 0000000..ee71d10 --- /dev/null +++ b/templates/manage/pixel.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + + +{% block body %} + +{% include "navbar.html" %} +

Pixel {{ pixel_name }}

+
+ + +
+
+ + +
+

Hits for pixel

+ + + + + + + + + + + {% for (date, ip, user_agent, ) in hits %} + + + + + + + {% endfor %} + +
#DateIPUser agent
{{ loop.index }}{{ date|escape }}{{ ip|escape }}{{ user_agent|escape }}
+{% endblock %} \ No newline at end of file diff --git a/templates/navbar.html b/templates/navbar.html new file mode 100644 index 0000000..b4267bb --- /dev/null +++ b/templates/navbar.html @@ -0,0 +1,10 @@ + \ No newline at end of file