commit 88209d88236c3d865a9f5174a0dced31920859bf Author: Tyler Murphy Date: Thu Jan 26 17:29:16 2023 -0500 i did things diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bce9d97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +xssbook.db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f275ca6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1063 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "async-trait" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[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 = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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 = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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 = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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 = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "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-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[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 = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[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 = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +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", +] + +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "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 = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tokio" +version = "1.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-cookies" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efe6d9c78aae53650a340d0e702e4f103582dce6af6bf897dc1b56c8ed5d4f6" +dependencies = [ + "async-trait", + "axum-core", + "cookie", + "futures-util", + "http", + "parking_lot", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[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-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[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 = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "xssbook" +version = "0.0.1" +dependencies = [ + "axum", + "bytes", + "rand", + "rusqlite", + "serde", + "serde_json", + "time", + "tokio", + "tower", + "tower-cookies", + "tower-http", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ce2f3f3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "xssbook" +version = "0.0.1" +edition = "2021" + +[dependencies] +tokio = { version = "1.23.0", features = ["full"] } +axum = { version = "0.6.4", features = ["headers"] } +tower-http = { version = "0.3.5", features = ["fs"] } +tower-cookies = "0.8.0" +tower = "0.4.13" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0" +rusqlite = { version = "0.28.0", features = ["bundled"] } +rand = "0.8.5" +time = "0.3.17" +bytes = "1.3.0" \ No newline at end of file diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..04ddadc --- /dev/null +++ b/public/404.html @@ -0,0 +1,18 @@ + + + + + + + + XSSBook - Not Found + + + +
+ + Page not found. +
+ \ No newline at end of file diff --git a/public/css/404.css b/public/css/404.css new file mode 100644 index 0000000..38035a4 --- /dev/null +++ b/public/css/404.css @@ -0,0 +1,20 @@ +body { + background-color: #f0f2f5; +} + +.error { + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 100vh; + flex-direction: column; +} + +.error .logo { + font-size: 100px; +} + +.desc { + font-size: 40px; +} \ No newline at end of file diff --git a/public/css/console.css b/public/css/console.css new file mode 100644 index 0000000..bc07969 --- /dev/null +++ b/public/css/console.css @@ -0,0 +1,60 @@ +body { + margin: 0; + padding: 0; + background-color: #181818; + display: flex; + flex-direction: column-reverse; +} + +@font-face { + font-family: sfpro; + src: url("../fonts/sfpro.otf") format("opentype"); +} + +div { + background-color: #282828; + font-family: sfpro; + margin: 15px; + margin-bottom: 0px; + border-radius: 5px; + padding: 10px; + width: calc(100% - 50px) +} + +span { + display: inline-block; + padding: 0; + margin: 0; + color: #ffffff; + font-family: sfpro; + margin-right: 10px; +} + +.json span { + display: inline; + margin: 0; +} + +.key { + color: white; +} + +.value { + color: white; +} + +.boolean { + color: aqua; +} + +.null { + color: blue; +} + +.number { + color: yellow; +} + +.string { + color: #4ae04a +} \ No newline at end of file diff --git a/public/css/header.css b/public/css/header.css new file mode 100644 index 0000000..a491f33 --- /dev/null +++ b/public/css/header.css @@ -0,0 +1,55 @@ +#header { + height: 3.5em; + background-color: white; + position: fixed; + width: 100vw; + box-shadow: 0 2px 4px rgba(0, 0, 0, .05), 0 8px 16px rgba(0, 0, 0, .05); + display: flex; + align-items: center; + justify-content: space-between; + z-index: 5; +} + +.spacer { + margin-bottom: 5em; +} + +#header .logo { + position: absolute; + font-size: 2.5em; + padding-left: .5em; + padding-top: .2em; +} + +#header .buttons { + flex: 1; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +#header .buttons a { + padding: 0px 50px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: #606770; +} + +#header .buttons a:hover { + background-color: #dddfe2; +} + +.selected { + color: #1778f2 !important; + border-bottom: 3px solid #1778f2; +} + +#header .pfp, #header .pfp img { + position: absolute; + right: 1em; + top: .5em; +} \ No newline at end of file diff --git a/public/css/home.css b/public/css/home.css new file mode 100644 index 0000000..33d72c0 --- /dev/null +++ b/public/css/home.css @@ -0,0 +1,182 @@ +body { + background-color: #f0f2f5; +} + +#posts { + display: flex; + flex-direction: column; + align-items: center; +} + +.post, .create { + width: 40em; + height: fit-content; + background-color: white; + border-radius: 10px; + padding: 15px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .05); + margin-bottom: 1.5em; +} + +.post { + padding-bottom: 0; +} + +.create { + display: flex; + flex-direction: row; +} + +.create button { + all: unset; + background-color: #f0f2f5; + border-radius: 3em; + margin-left: 1em; + flex: 1; +} + +.create button:hover { + background-color: #e4e6e8; + cursor: pointer; +} + +.create button p { + margin-left: 1em; + font-size: 18px; +} + +.postheader { + display: flex; + flex-direction: row; + margin-bottom: .5em; +} + +.postbuttons { + display: flex; + flex-direction: row; +} + +.postbuttons>span { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 7px; + border-radius: 5px; + color: #606770; + margin: 3px +} + +.postbuttons>span:hover { + background-color: #e4e6e8; +} + +.postname { + margin-left: 1em; + display: flex; + flex-direction: column; +} + +.icons { + background-image: url('data:image/png;base64,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'); + display: inline-block; + width: 18px; + height: 18px; + background-size: auto; + background-repeat: no-repeat; + margin-right: .5em; + filter: invert(39%) sepia(21%) saturate(200%) saturate(109.5%) hue-rotate(174deg) brightness(94%) contrast(86%); +} + +.blue { + filter: invert(39%) sepia(57%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(147.75%) hue-rotate(202deg) brightness(97%) contrast(96%); +} + +.like { + background-position: 0px -132px; +} + +.comm { + background-position: 0px -113px; +} + +.createpost { + position: relative; + background-color: white; + width: 450px; + padding: 20px; + border-radius: .5em; + box-shadow: 0 2px 4px rgba(0, 0, 0, .1), 0 8px 16px rgba(0, 0, 0, .1); +} + +.createpost .postheader { + margin-top: 20px; +} + +.createpost textarea { + border: none; + resize: none; + outline: none; + font-family: sfpro; + font-size: 24px; + margin-top: 10px; + width: 100%; + height: 120px; + margin-bottom: 20px; +} + +.createpost>span { + margin-top: -10px; +} + +.primary { + margin: 0; + width: calc(100% - 20px); + font-family: sfprobold; + height: 15px; +} + +.close { + top: 1em; + right: 1em; +} + +.comment { + display: flex; + flex-direction: row; + margin-bottom: 10px; +} + +.comment p, .post p { + word-break: break-all; + white-space: normal; +} + +.comment>span { + display: flex; + flex-direction: column; + margin-left: 10px; + background-color: #f0f2f5; + border-radius: 10px; + padding: 10px; + padding-right: 10px; +} + +.larger { + font-size: 18px; +} + +.newcomment { + display: flex; + flex-direction: row; +} + +#comments input { + all: unset; + padding: 10px; + border-radius: 10px; + width: calc(100% - 20px); + background-color: #f0f2f5; + font-family: sfpro; +} \ No newline at end of file diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..7e5cde7 --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,47 @@ +.login { + background-color: #f0f2f5; + display: flex; + justify-content: center; + align-content: center; + flex-direction: row; + padding: 8em 0em; +} + +.prompt { + display: flex; + position: relative; + flex-direction: column; + background-color: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, .1), 0 8px 16px rgba(0, 0, 0, .1); + border-radius: 8px; + width: 396px; + padding: 10px +} + +.show { + display: flex; + flex-direction: column; + justify-content: center; +} + +.login-button { + margin-bottom: 10px; + font-size: 20px; +} + +.newacc { + margin: 10px 70px; + margin-bottom: 20px; +} + +.signacc { + margin: 10px 70px; + margin-bottom: 0; +} + +@media (max-aspect-ratio: 2/3) { + .login { + flex-direction: column; + align-items: center; + } +} \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..c1b4fa2 --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,307 @@ +body { + background-color: white; + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; +} + +@font-face { + font-family: facebook; + src: url("../fonts/facebook.otf") format("opentype"); +} + +@font-face { + font-family: sfpro; + src: url("../fonts/sfpro.otf") format("opentype"); +} + +@font-face { + font-family: sfprobold; + src: url("../fonts/sfprobold.otf") format("opentype"); +} + +.logo { + color: #1778f2; + font-size: 3.5em; + font-family: facebook; +} + +.text { + font-family: sfpro; + font-size: 28px; + font-weight: normal; + line-height: 32px; + width: 500px; +} + +.btext { + font-family: sfpro; + color: #1778f2 +} + +.error { + font-family: sfpro; + color: #f02849; + padding-top: 10px; + margin-bottom: -10px; + font-size: 15px; +} + +.gtext { + font-family: sfpro; + color: #606770 +} + +.label { + font-family: sfpro; + color: #606770; + font-size: 15px; + padding-top: 10px; + padding-left: 10px; +} + +.stext { + font-family: sfpro; + font-size: 10px; +} + +.mtext { + font-family: sfpro; + font-size: 15px; +} + +.ltext { + font-family: sfpro; + font-size: 22px; +} + +.ctext { + display: block; + font-family: sfpro; + text-align: center; +} + +a { + color: inherit; + text-decoration: none; + cursor: pointer; +} + +p { + padding: 0; + margin: 0; +} + +span { + padding: 0; + margin: 0; +} + +footer { + bottom: 0; + height: 400px; + background-color: white; +} + +input { + flex: 1; + font-family: sfpro; + background-color: white; + padding: 10px; + margin: 10px; + margin-bottom: 0; + border-radius: 5px; + border: 1px solid #dddfe2; + color: #1d2129; + font-size: 18px; +} + +.radiomenu { + display: flex; + flex-wrap: wrap; +} + +.radiomenu span { + display: inline-block; + position: relative; + font-family: sfpro; + background-color: white; + margin: 10px; + margin-bottom: 0; + border-radius: 5px; + border: 1px solid #dddfe2; + color: #1d2129; + font-size: 15px; + flex: 1 0 auto; +} + +.radiomenu span label { + padding: 10px; + display: block; + box-sizing: border-box; + width: auto; + color: #1d2129; +} + +[type="radio"] { + height: 40px; + margin: 0; + position: absolute; + right: 10px; + top: 0; + text-align: left; +} + +select { + all: unset; + flex: 1; + font-family: sfpro; + background-color: white; + padding: 10px; + margin: 10px; + margin-bottom: 0; + border-radius: 5px; + border: 1px solid #dddfe2; + color: #1d2129; + font-size: 15px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAADFBMVEVMaXEFBQUFBQUFBQXG+MOgAAAAA3RSTlMAn3BcqiM3AAAAOUlEQVQIW53BsQ2AMAwAMJelV6C80qPYw4k9JmskbqA2px5uNIlcoxF7FmbFxuhckA2iwNzgev3wAR4FDUQbc/qhAAAAAElFTkSuQmCC"); + background-position: right 10px center; + background-repeat: no-repeat; + background-size: 15px; +} + +input:focus { + border: 1px solid #1778f2; +} + +.primary { + all: unset; + font-family: sfpro; + background-color: #1778f2; + color: white; + padding: 10px; + margin: 20px; + border-radius: 5px; + padding-bottom: 15px; + text-align: center; + cursor: pointer; +} + +.success { + all: unset; + font-family: sfpro; + background-color: #42b72a; + color: white; + padding: 10px; + margin-left: 10px; + margin-right: 10px; + border-radius: 5px; + text-align: center; + cursor: pointer; +} + +.bold { + font-family: sfprobold; +} + +.line { + width: calc(100% - 40px); + margin-left: 20px; + margin-right: 20px; + border-bottom: 1px solid #dadde1; + margin-bottom: 10px; + margin-top: 10px; +} + +.fullline { + width: calc(100%); + border-bottom: 1px solid #dadde1; + margin-bottom: 10px; + margin-top: 10px; +} + +footer { + text-align: center; + font-family: sfpro; + padding-top: 30px; + padding-bottom: 30px; + font-size: 13px; + color: #737373; +} + +#popup { + position: absolute; + width: 100vw; + height: 100vh; + background-color: rgba(255, 255, 255, .8); + margin: 0; + padding: 0; + top: 0; + display: flex; + justify-content: center; + align-items: center; +} + +.row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.row input { + width: 50% +} + +.close { + position: absolute; + z-index: 2; + width: 20px; + height: 20px; + right: 12px; + top: 12px; + cursor: pointer; + background-size: 20px; + background-position: right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAADFBMVEVgZ3FHcExgZ3FgZ3Fd28LEAAAAA3RSTlOfAHBcPEovAAAASklEQVQIW2MIBQMG7FRmaGgmUG5PaKgdkNovGvIPSPkfDf4IpII/+h8FUiF/7EVB2u3/gE3x/4hMQQShSqAaoNqhhkGNhlqE2y0A1E85Y0JErBoAAAAASUVORK5CYII='); +} + +.hidden { + visibility: hidden; + pointer-events: none; + display: none !important; +} + +.pfp, .pfp img { + display: block; + width: 2.5em; + height: 2.5em; + border-radius: 3em; + background-color: #e4e6e8; + flex-shrink: 0; +} + +.nb { + margin-bottom: 0; +} + +form { + all: unset; + border-radius: 10px; + margin-left: 10px; + width: 100%; +} + +#load { + width: 100%; + display: flex; + justify-content: center; + padding-bottom: 20px; +} + +#load a:hover { + border-bottom: #606770 1px solid; +} \ No newline at end of file diff --git a/public/css/people.css b/public/css/people.css new file mode 100644 index 0000000..b8cf025 --- /dev/null +++ b/public/css/people.css @@ -0,0 +1,44 @@ +body { + background-color: #f0f2f5; +} + +#users { + display: flex; + flex-direction: column; + align-items: center; +} + +.person { + width: 30em; + height: fit-content; + background-color: white; + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .05); + margin-bottom: 1.5em; + display: flex; + flex-direction: row; +} + +.profile, .profile img { + border-radius: 10px 0px 0px 10px; + width: 10em; + height: 10em; + padding: 0; + display: block; + background-color: #e4e6e8; + flex-shrink: 0; +} + +.info { + margin: 20px; + display: flex; + flex-direction: column; +} + +.info span { + width: 280px; + margin-bottom: 5px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} \ No newline at end of file diff --git a/public/css/profile.css b/public/css/profile.css new file mode 100644 index 0000000..4c5ae10 --- /dev/null +++ b/public/css/profile.css @@ -0,0 +1,121 @@ +body { + background-color: #f0f2f5; +} + +.spacer { + margin-bottom: 3.5em; +} + +#top { + background-color: white; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + box-shadow: 0 2px 4px rgba(0, 0, 0, .05); +} + +#banner { + background-image: linear-gradient(#949494, white, white); + height: 30em; + width: 100%; + display: flex; + justify-content: center; +} + +#banner div, #banner img { + width: 80em; + height: inherit; + background-color: #e4e6e8; + border-radius: 0px 0px 20px 20px; +} + +#info { + width: 80em; + display: flex; + flex-direction: row; +} + +.face { + background-color: #e4e6e8; + height: 12em; + width: 12em; + border-radius: 7em; + border: solid 5px white; + margin-top: -2em; + margin-left: 2em; + margin-right: 2em; +} + +.infodata { + margin-top: 2em; + display: flex; + flex-direction: column; +} + +.infodata span { + margin-bottom: .5em; +} + +.profilebuttons { + width: 80em; + height: 3em; + display: flex; + align-items: center; +} + +.profilebuttons button { + all: unset; + font-family: sfprobold; + padding: 0px 50px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: #606770; + cursor: pointer; +} + +.profilebuttons button:hover { + background-color: #dddfe2; +} + +.selected { + color: #1778f2 !important; + border-bottom: 3px solid #1778f2 !important; +} + +#about { + margin-top: 2em; + align-self: center; + padding: 0; + display: flex; + flex-direction: row; +} + +#posts { + margin-top: 2em; +} + +#about .ltext { + border-right: 2px solid #dadde1; + padding: 10px; + padding-right: 3em; +} + +#about .data { + display: flex; + flex-direction: column; + padding: 10px; + padding-left: 20px; + padding-top: 15px; +} + +#about .data span { + margin-bottom: 10px; + width: 28em; + margin-bottom: 5px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} \ No newline at end of file diff --git a/public/fonts/facebook.otf b/public/fonts/facebook.otf new file mode 100755 index 0000000..97d5c6f Binary files /dev/null and b/public/fonts/facebook.otf differ diff --git a/public/fonts/sfpro.otf b/public/fonts/sfpro.otf new file mode 100644 index 0000000..09aaca9 Binary files /dev/null and b/public/fonts/sfpro.otf differ diff --git a/public/fonts/sfprobold.otf b/public/fonts/sfprobold.otf new file mode 100644 index 0000000..025b25c Binary files /dev/null and b/public/fonts/sfprobold.otf differ diff --git a/public/home.html b/public/home.html new file mode 100644 index 0000000..865e53a --- /dev/null +++ b/public/home.html @@ -0,0 +1,17 @@ + + + + + + + + XSSBook - Home + + + + + + + + + \ No newline at end of file diff --git a/public/js/api.js b/public/js/api.js new file mode 100644 index 0000000..07769f6 --- /dev/null +++ b/public/js/api.js @@ -0,0 +1,63 @@ +const endpoint = '/api' + +const request = async (url, body, method) => { + if (method === undefined) method = 'POST' + const response = await fetch(endpoint + url, { + method, + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json' + } + }); + if (response.status == 401) { + location.href = 'login' + } + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + const json = await response.json() + return { status: response.status, msg: json.msg, json } + } else { + const msg = await response.text(); + return { status: response.status, msg } + } +} + +const login = async (email, password) => { + return await request('/auth/login', {email, password}) +} + +const register = async (firstname, lastname, email, password, gender, day, month, year) => { + return await request('/auth/register', {firstname, lastname, email, password, gender, day, month, year}) +} + +const loadpostspage = async (page) => { + return await request('/posts/page', {page}) +} + +const loadusersposts = async (user_id) => { + return await request('/posts/user', {user_id}) +} + +const loadusers = async (ids) => { + return await request('/users/load', {ids}) +} + +const loaduserspage = async (page) => { + return await request('/users/page', {page}) +} + +const loadself = async () => { + return await request("/users/self", {}) +} + +const postcomment = async (post_id, content) => { + return await request('/posts/comment', {post_id, content}, 'PATCH') +} + +const postlike = async (post_id, state) => { + return await request('/posts/like', {post_id, state}, 'PATCH') +} + +const createpost = async (content) => { + return await request('/posts/create', {content}) +} \ No newline at end of file diff --git a/public/js/header.js b/public/js/header.js new file mode 100644 index 0000000..8fe03e5 --- /dev/null +++ b/public/js/header.js @@ -0,0 +1,25 @@ +function header(home, people) { + const html = ` + +
+ ` + + add(html, 'header') +} \ No newline at end of file diff --git a/public/js/home.js b/public/js/home.js new file mode 100644 index 0000000..fd40ebf --- /dev/null +++ b/public/js/home.js @@ -0,0 +1,233 @@ +const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +function parseDate(date) { + return months[date.getUTCMonth()] + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString(); +} + +function parseComment(comment) { + const author = data.users[comment[0]] + if (author === undefined) { + author = {} + } + const html = ` +
+ + + + + ${author.firstname + ' ' + author.lastname} +

${comment[1]}

+
+
+ ` + return html +} + +function parsePost(post) { + console.log(post.likes) + const author = data.users[post.user_id] + if (author === undefined) { + author = {} + } + const html = ` +
+
+ + + +
+ ${author.firstname + ' ' + author.lastname} + ${parseDate(new Date(post.date))} +
+
+

+ ${post.content.replace(/\n/g,'
')} +

+ + ${Object.keys(post.likes).map(k => post.likes[k]).filter(v => v !== false).length} Likes + +
+
+ + + Like + + + + Comment + +
+
+
+ ${post.comments.map(parseComment).join('')} +
+ + + +
+ +
+
+
+
+ ` + + return html +} + +function getPost(post_id) { + for (let i = 0; i < data.posts.length; i++) { + if (data.posts[i].post_id === post_id) { + return i + } + } + return -1 +} + +async function like(span) { + const id = parseInt(span.parentElement.parentElement.getAttribute('postid')) + const post = data.posts[getPost(id)] + const index = post.likes.indexOf(data.user.user_id) + const current = index !== -1 + const response = await postlike(id, !current) + if (response.status != 200) return; + if (current) { + post.likes.splice(index, 1) + } else { + post.likes.push(data.user.user_id) + } + render() +} + +async function comment(event) { + event.preventDefault(); + const text = event.target.elements.text.value.trim(); + if (text.length < 1) return; + const id = parseInt(event.target.parentElement.parentElement.parentElement.getAttribute('postid')) + var index = getPost(id); + if (index === -1) return; + const response = await postcomment(id, text) + if (response.status != 200) return; + event.target.elements.text.value = ''; + data.posts[index].comments.push([data.user.user_id, text]) + render() +} + +async function post() { + const text = document.getElementById("text").value.trim() + const error = document.getElementsByClassName('error')[0] + if (text.length < 1) return; + const response = await createpost(text); + if (response.status != 201) { + error.innerHTML = response.msg + return; + } + error.innerHTML = ''; + data.posts.unshift({ + post_id: response.msg, + user_id: data.user.user_id, + date: Date.now(), + content: text, + likes: [], + comments: [] + }) + render() +} + +function render() { + const html = ` +
+
+ + + + +
+ ${data.posts.map(p => parsePost(p)).join('')} +
+ ` + + add(html, 'posts') + + const popup = ` + + ` + + add(popup, 'popup') + + const load = ` +
+ Load more posts +
+ ` + + if (page !== -1) { + add(load, 'load') + } else { + remove('load') + } +} + +var page = 0 +const data = { + user: {}, + users: {}, + posts: [] +} + +async function load() { + const posts = (await loadpostspage(page)).json + if (posts.length === 0) { + page = -1 + } else { + page++ + } + data.posts.push(... posts) + const batch = [] + for (const post of posts) { + for(const comment of post.comments) { + if (data.users[comment[0]] !== undefined) continue + if (batch.includes(comment[0])) continue + batch.push(comment[0]) + } + if (data.users[post.user_id] !== undefined) continue + if (batch.includes(post.user_id)) continue + batch.push(post.user_id) + } + const users = (await loadusers(batch)).json + for (const id in users) { + data.users[id] = users[id] + } + render() +} + + +async function init() { + header(true, false) + data.user = (await loadself()).json + data.users[data.user.user_id] = data.user + load() +} \ No newline at end of file diff --git a/public/js/login.js b/public/js/login.js new file mode 100644 index 0000000..f65808b --- /dev/null +++ b/public/js/login.js @@ -0,0 +1,29 @@ +async function onlogin() { + const email = document.getElementById('email').value + const password = document.getElementById('pass').value + const response = await login(email, password) + if (response.status !== 200) { + const error = document.getElementsByClassName('error')[0] + error.innerHTML = response.msg + } else { + location.href = '/home' + } +} + +async function onregister() { + const first = document.getElementById('firstname').value + const last = document.getElementById('lastname').value + const email = document.getElementById('newemail').value + const pass = document.getElementById('newpass').value + const month = document.getElementById('month').value + const day = document.getElementById('day').value + const year = document.getElementById('year').value + const gender = document.querySelector('input[name="gender"]:checked').value + const response = await register(first, last, email, pass, gender, parseInt(day), parseInt(month), parseInt(year)) + if (response.status !== 200) { + const error = document.getElementsByClassName('error')[1] + error.innerHTML = response.msg + } else { + location.href = '/home' + } +} \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..0003c0d --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,22 @@ +var range; + +function add(html, id) { + const old = document.getElementById(id) + if (old !== null) { + old.remove() + } + if (range === undefined) { + var range = document.createRange() + range.setStart(document.body, 0) + } + document.body.appendChild( + range.createContextualFragment(html) + ) +} + +function remove(id) { + const old = document.getElementById(id) + if (old !== null) { + old.remove() + } +} \ No newline at end of file diff --git a/public/js/people.js b/public/js/people.js new file mode 100644 index 0000000..ddd1875 --- /dev/null +++ b/public/js/people.js @@ -0,0 +1,65 @@ +const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +function parseDate(date) { + return months[date.getUTCMonth()] + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString(); +} + +function parseUser(user) { + const html = ` + +
+ +
+
+ ${user.firstname + ' ' + user.lastname} + Joined ${parseDate(new Date(user.date))} + Gender: ${user.gender} + Birthday: ${months[user.month] + ' ' + user.day + ', ' + user.year} + User ID: ${user.user_id} +
+
+ ` + return html +} + +function render() { + const html = ` +
+ ${data.users.map(u => parseUser(u)).join('')} +
+ ` + + add(html, 'users') + + const load = ` +
+ Load more users +
+ ` + + if (page !== -1) { + add(load, 'load') + } else { + remove('load') + } +} + +var page = 0 +var data = { + users: [] +} + +async function load() { + const users = (await loaduserspage(page)).json + if (users.length === 0) { + page = -1 + } else { + page++ + } + data.users.push(... users) + render() +} + +header(false, true) +load() \ No newline at end of file diff --git a/public/js/profile.js b/public/js/profile.js new file mode 100644 index 0000000..79dbe2f --- /dev/null +++ b/public/js/profile.js @@ -0,0 +1,88 @@ +function render() { + const html = ` +
+ +
+
+ +
+
+ ${data.user.firstname + ' ' + data.user.lastname} + Joined ${parseDate(new Date(data.user.date))} +
+
+
+
+ + +
+
+ ` + + add(html, 'top') + + const postsh = ` +
+ ${data.posts.map(p => parsePost(p)).join('')} +
+ ` + + add(postsh, 'posts') + + const about = ` +
+ About +
+ Name: ${data.user.firstname + ' ' + data.user.lastname} + Email: ${data.user.email} + Gender: ${data.user.gender} + Birthday: ${months[data.user.month] + ' ' + data.user.day + ', ' + data.user.year} + User ID: ${data.user.user_id} +
+
+ ` + + add(about, 'about') +} + +var posts = true + +async function load() { + header(false, false) + + var params = {}; + for (const [key, value] of new URLSearchParams(location.search)) { + params[key] = value + } + + const id = params.id !== undefined && !isNaN(params.id) ? parseInt(params.id) : (await loadself()).json.user_id + const posts = (await loadusersposts(id)).json + data.posts.push(... posts) + const batch = [id] + for (const post of posts) { + for(const comment of post.comments) { + if (data.users[comment[0]] !== undefined) continue + if (batch.includes(comment[0])) continue + batch.push(comment[0]) + } + if (data.users[post.user_id] !== undefined) continue + if (batch.includes(post.user_id)) continue + batch.push(post.user_id) + } + const users = (await loadusers(batch)).json + for (const user of users) { + data.users[user.user_id] = user + } + data.user = data.users[id] + render() +} + +load() \ No newline at end of file diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000..97398f9 --- /dev/null +++ b/public/login.html @@ -0,0 +1,170 @@ + + + + + + + + + XSSBook - Login + + +
+
+ +

Connect with javascript and the world around you on XSSBook.

+
+
+ + + + + Forgot Password? +
+ +
+ +
+ + + \ No newline at end of file diff --git a/public/people.html b/public/people.html new file mode 100644 index 0000000..399751a --- /dev/null +++ b/public/people.html @@ -0,0 +1,15 @@ + + + + + + + + XSSBook - People + + + + + + + \ No newline at end of file diff --git a/public/profile.html b/public/profile.html new file mode 100644 index 0000000..d17ab09 --- /dev/null +++ b/public/profile.html @@ -0,0 +1,17 @@ + + + + + + + + + XSSBook - Profile + + + + + + + + \ No newline at end of file diff --git a/src/api/auth.rs b/src/api/auth.rs new file mode 100644 index 0000000..d60483f --- /dev/null +++ b/src/api/auth.rs @@ -0,0 +1,98 @@ +use axum::{Router, routing::post, response::Response}; +use serde::Deserialize; +use time::{OffsetDateTime, Duration}; +use tower_cookies::{Cookies, Cookie}; + +use crate::types::{user::User, response::ResponseCode, session::Session, extract::{Json, AuthorizedUser}}; + +#[derive(Deserialize)] +struct RegistrationRequet { + firstname: String, + lastname: String, + email: String, + password: String, + gender: String, + day: u8, + month: u8, + year: u32 +} + + +async fn register(cookies: Cookies, Json(body): Json) -> Response { + + let user = match User::new(body.firstname, body.lastname, body.email, body.password, body.gender, body.day, body.month, body.year) { + Ok(user) => user, + Err(err) => return err + }; + + let session = match Session::new(user.user_id) { + Ok(session) => session, + Err(err) => return err + }; + + let mut now = OffsetDateTime::now_utc(); + now += Duration::weeks(52); + + let mut cookie = Cookie::new("auth", session.token); + cookie.set_secure(false); + cookie.set_http_only(false); + cookie.set_expires(now); + cookie.set_path("/"); + + cookies.add(cookie); + + ResponseCode::Created.msg("Successfully created new user") +} + +#[derive(Deserialize)] +struct LoginRequest { + email: String, + password: String, +} + +async fn login(cookies: Cookies, Json(body): Json) -> Response { + + let Ok(user) = User::from_email(&body.email) else { + return ResponseCode::BadRequest.msg("Email is not registered") + }; + + if user.password != body.password { + return ResponseCode::BadRequest.msg("Password is not correct") + } + + let session = match Session::new(user.user_id) { + Ok(session) => session, + Err(err) => return err + }; + + let mut now = OffsetDateTime::now_utc(); + now += Duration::weeks(52); + + let mut cookie = Cookie::new("auth", session.token); + cookie.set_secure(false); + cookie.set_http_only(false); + cookie.set_expires(now); + cookie.set_path("/"); + + cookies.add(cookie); + + ResponseCode::Success.msg("Successfully logged in") +} + +async fn logout(cookies: Cookies, AuthorizedUser(user): AuthorizedUser) -> Response { + + cookies.remove(Cookie::new("auth", "")); + + if let Err(err) = Session::delete(user.user_id) { + return err + } + + ResponseCode::Success.msg("Successfully logged out") +} + +pub fn router() -> Router { + Router::new() + .route("/register", post(register)) + .route("/login", post(login)) + .route("/logout", post(logout)) +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..ba38aeb --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod pages; +pub mod posts; +pub mod users; \ No newline at end of file diff --git a/src/api/pages.rs b/src/api/pages.rs new file mode 100644 index 0000000..749a686 --- /dev/null +++ b/src/api/pages.rs @@ -0,0 +1,58 @@ +use axum::{Router, response::{Response, Redirect, IntoResponse}, routing::get}; + +use crate::types::{extract::AuthorizedUser, response::ResponseCode}; + +async fn root(user: Option) -> Response { + println!("{}", user.is_some()); + if user.is_some() { + return Redirect::to("/home").into_response() + } else { + return Redirect::to("/login").into_response() + } +} + +async fn login(user: Option) -> Response { + if user.is_some() { + return Redirect::to("/home").into_response() + } else { + return ResponseCode::Success.file("/login.html").await.unwrap() + } +} + +async fn home(user: Option) -> Response { + if user.is_none() { + return Redirect::to("/login").into_response() + } else { + return ResponseCode::Success.file("/home.html").await.unwrap() + } +} + +async fn people(user: Option) -> Response { + if user.is_none() { + return Redirect::to("/login").into_response() + } else { + return ResponseCode::Success.file("/people.html").await.unwrap() + } +} + +async fn profile(user: Option) -> Response { + if user.is_none() { + return Redirect::to("/login").into_response() + } else { + return ResponseCode::Success.file("/profile.html").await.unwrap() + } +} + +async fn wordpress() -> Response { + ResponseCode::ImATeapot.msg("Hello i am a teapot owo") +} + +pub fn router() -> Router { + Router::new() + .route("/", get(root)) + .route("/login", get(login)) + .route("/home", get(home)) + .route("/people", get(people)) + .route("/profile", get(profile)) + .route("/wp-admin", get(wordpress)) +} \ No newline at end of file diff --git a/src/api/posts.rs b/src/api/posts.rs new file mode 100644 index 0000000..405dfa6 --- /dev/null +++ b/src/api/posts.rs @@ -0,0 +1,102 @@ +use axum::{response::Response, Router, routing::{post, patch}}; +use serde::Deserialize; + +use crate::types::{extract::{AuthorizedUser, Json}, post::Post, response::ResponseCode}; + + +#[derive(Deserialize)] +struct PostCreateRequest { + content: String +} + +async fn create(AuthorizedUser(user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(_post) = Post::new(user.user_id, body.content) else { + return ResponseCode::InternalServerError.msg("Failed to create post") + }; + + ResponseCode::Created.msg("Successfully created new post") +} + +#[derive(Deserialize)] +struct PostPageRequest { + page: u64 +} + +async fn page(AuthorizedUser(_user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(posts) = Post::from_post_page(body.page) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + let Ok(json) = serde_json::to_string(&posts) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + ResponseCode::Success.json(&json) +} + +#[derive(Deserialize)] +struct UsersPostsRequest { + user_id: u64 +} + +async fn user(AuthorizedUser(_user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(posts) = Post::from_user_id(body.user_id) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + let Ok(json) = serde_json::to_string(&posts) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + ResponseCode::Success.json(&json) +} + +#[derive(Deserialize)] +struct PostCommentRequest { + content: String, + post_id: u64 +} + +async fn comment(AuthorizedUser(user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(mut post) = Post::from_post_id(body.post_id) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + if let Err(err) = post.comment(user.user_id, body.content) { + return err; + } + + ResponseCode::Success.msg("Successfully commented on post") +} + +#[derive(Deserialize)] +struct PostLikeRequest { + state: bool, + post_id: u64 +} + +async fn like(AuthorizedUser(user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(mut post) = Post::from_post_id(body.post_id) else { + return ResponseCode::InternalServerError.msg("Failed to fetch posts") + }; + + if let Err(err) = post.like(user.user_id, body.state) { + return err; + } + + ResponseCode::Success.msg("Successfully changed like status on post") +} + +pub fn router() -> Router { + Router::new() + .route("/create", post(create)) + .route("/page", post(page)) + .route("/user", post(user)) + .route("/comment", patch(comment)) + .route("/like", patch(like)) +} \ No newline at end of file diff --git a/src/api/users.rs b/src/api/users.rs new file mode 100644 index 0000000..283ec96 --- /dev/null +++ b/src/api/users.rs @@ -0,0 +1,52 @@ +use axum::{Router, response::Response, routing::post}; +use serde::Deserialize; +use crate::types::{extract::{AuthorizedUser, Json}, response::ResponseCode, user::User}; + +#[derive(Deserialize)] +struct UserLoadRequest { + ids: Vec +} + +async fn load_batch(AuthorizedUser(_user): AuthorizedUser, Json(body): Json) -> Response { + + let users = User::from_user_ids(body.ids); + let Ok(json) = serde_json::to_string(&users) else { + return ResponseCode::InternalServerError.msg("Failed to fetch users") + }; + + ResponseCode::Success.json(&json) +} + +#[derive(Deserialize)] +struct UserPageReqiest { + page: u64 +} + +async fn load_page(AuthorizedUser(_user): AuthorizedUser, Json(body): Json) -> Response { + + let Ok(users) = User::from_user_page(body.page) else { + return ResponseCode::InternalServerError.msg("Failed to fetch users") + }; + + let Ok(json) = serde_json::to_string(&users) else { + return ResponseCode::InternalServerError.msg("Failed to fetch users") + }; + + ResponseCode::Success.json(&json) +} + +async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { + + let Ok(json) = serde_json::to_string(&user) else { + return ResponseCode::InternalServerError.msg("Failed to fetch user") + }; + + ResponseCode::Success.json(&json) +} + +pub fn router() -> Router { + Router::new() + .route("/load", post(load_batch)) + .route("/self", post(load_self)) + .route("/page", post(load_page)) +} \ No newline at end of file diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000..7227074 --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1,16 @@ +use rusqlite::Result; + +pub mod posts; +pub mod users; +pub mod sessions; + +pub fn connect() -> Result { + return rusqlite::Connection::open("xssbook.db"); +} + +pub fn init() -> Result<()> { + users::init()?; + posts::init()?; + sessions::init()?; + Ok(()) +} \ No newline at end of file diff --git a/src/database/posts.rs b/src/database/posts.rs new file mode 100644 index 0000000..77d2387 --- /dev/null +++ b/src/database/posts.rs @@ -0,0 +1,94 @@ +use std::collections::HashSet; +use std::time::{SystemTime, UNIX_EPOCH}; + +use rusqlite::{OptionalExtension, Row}; + +use crate::types::post::Post; +use crate::database; + +pub fn init() -> Result<(), rusqlite::Error> { + let sql = " + CREATE TABLE IF NOT EXISTS posts ( + post_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + content TEXT NOT NULL, + likes TEXT NOT NULL, + comments TEXT NOT NULL, + date INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(user_id) + ); + "; + let conn = database::connect()?; + conn.execute(sql, ())?; + Ok(()) +} + +fn post_from_row(row: &Row) -> Result { + let post_id = row.get(0)?; + let user_id = row.get(1)?; + let content = row.get(2)?; + let likes_json: String = row.get(3)?; + let comments_json: String = row.get(4)?; + let date = row.get(5)?; + + let Ok(likes) = serde_json::from_str(&likes_json) else { + return Err(rusqlite::Error::InvalidQuery) + }; + + let Ok(comments) = serde_json::from_str(&comments_json) else { + return Err(rusqlite::Error::InvalidQuery) + }; + + Ok(Post{post_id, user_id, content, likes, comments, date}) +} + +pub fn get_post(post_id: u64) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM posts WHERE post_id = ?")?; + let row = stmt.query_row([post_id], |row| Ok(post_from_row(row)?)).optional()?; + Ok(row) +} + +pub fn get_post_page(page: u64) -> Result, rusqlite::Error> { + let page_size = 10; + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM posts ORDER BY post_id DESC LIMIT ? OFFSET ?")?; + let row = stmt.query_map([page_size, page_size * page], |row| Ok(post_from_row(row)?))?; + Ok(row.into_iter().flatten().collect()) +} + +pub fn get_users_posts(user_id: u64) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM posts WHERE user_id = ? ORDER BY post_id DESC")?; + let row = stmt.query_map([user_id], |row| Ok(post_from_row(row)?))?; + Ok(row.into_iter().flatten().collect()) +} + +pub fn add_post(user_id: u64, content: &str) -> Result { + let likes: HashSet = HashSet::new(); + let comments: Vec<(u64, String)> = Vec::new(); + let Ok(likes_json) = serde_json::to_string(&likes) else { + return Err(rusqlite::Error::InvalidQuery) + }; + let Ok(comments_json) = serde_json::to_string(&comments) else { + return Err(rusqlite::Error::InvalidQuery) + }; + let date = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64; + let conn = database::connect()?; + let mut stmt = conn.prepare("INSERT INTO posts (user_id, content, likes, comments, date) VALUES(?,?,?,?,?) RETURNING *;")?; + let post = stmt.query_row((user_id, content, likes_json, comments_json, date), |row| Ok(post_from_row(row)?))?; + Ok(post) +} + +pub fn update_post(post_id: u64, likes: &HashSet, comments: &Vec<(u64, String)>) -> Result<(), rusqlite::Error> { + let Ok(likes_json) = serde_json::to_string(&likes) else { + return Err(rusqlite::Error::InvalidQuery) + }; + let Ok(comments_json) = serde_json::to_string(&comments) else { + return Err(rusqlite::Error::InvalidQuery) + }; + let conn = database::connect()?; + let sql = "UPDATE posts SET likes = ?, comments = ? WHERE post_id = ?"; + conn.execute(sql, (likes_json, comments_json, post_id))?; + Ok(()) +} \ No newline at end of file diff --git a/src/database/sessions.rs b/src/database/sessions.rs new file mode 100644 index 0000000..7866d76 --- /dev/null +++ b/src/database/sessions.rs @@ -0,0 +1,42 @@ +use rusqlite::OptionalExtension; + +use crate::{database, types::session::Session}; + +pub fn init() -> Result<(), rusqlite::Error> { + let sql = " + CREATE TABLE IF NOT EXISTS sessions ( + user_id INTEGER PRIMARY KEY NOT NULL, + token TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(user_id) + ); + "; + let conn = database::connect()?; + conn.execute(sql, ())?; + Ok(()) +} + +pub fn get_session(token: &str) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM sessions WHERE token = ?")?; + let row = stmt.query_row([token], |row| { + Ok(Session { + user_id: row.get(0)?, + token: row.get(1)?, + }) + }).optional()?; + Ok(row) +} + +pub fn set_session(user_id: u64, token: &str) -> Result<(), Box> { + let conn = database::connect()?; + let sql = "INSERT OR REPLACE INTO sessions (user_id, token) VALUES (?, ?);"; + conn.execute(sql, (user_id, token))?; + Ok(()) +} + +pub fn delete_session(user_id: u64) -> Result<(), Box> { + let conn = database::connect()?; + let sql = "DELETE FROM sessions WHERE user_id = ?;"; + conn.execute(sql, [user_id])?; + Ok(()) +} \ No newline at end of file diff --git a/src/database/users.rs b/src/database/users.rs new file mode 100644 index 0000000..2618dce --- /dev/null +++ b/src/database/users.rs @@ -0,0 +1,79 @@ +use std::time::{SystemTime, UNIX_EPOCH}; +use rusqlite::{OptionalExtension, Row}; + +use crate::{database, types::user::User}; + +pub fn init() -> Result<(), rusqlite::Error> { + let sql = " + CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER PRIMARY KEY AUTOINCREMENT, + firstname VARCHAR(20) NOT NULL, + lastname VARCHAR(20) NOT NULL, + email VARCHAR(50) NOT NULL, + password VARCHAR(50) NOT NULL, + gender VARCHAR(100) NOT NULL, + date BIGINT NOT NULL, + day TINYINT NOT NULL, + month TINYINT NOT NULL, + year INTEGER NOT NULL + ); + "; + let conn = database::connect()?; + conn.execute(sql, ())?; + Ok(()) +} + +fn user_from_row(row: &Row, hide_password: bool) -> Result { + let user_id = row.get(0)?; + let firstname = row.get(1)?; + let lastname = row.get(2)?; + let email = row.get(3)?; + let password = row.get(4)?; + let gender = row.get(5)?; + let date = row.get(6)?; + let day = row.get(7)?; + let month = row.get(8)?; + let year = row.get(9)?; + + let password = if hide_password { "".to_string() } else { password }; + + Ok(User{user_id, firstname, lastname, email, password, gender,date, day, month, year}) +} + +pub fn get_user_by_id(user_id: u64, hide_password: bool) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM users WHERE user_id = ?")?; + let row = stmt.query_row([user_id], |row| Ok(user_from_row(row, hide_password)?)).optional()?; + Ok(row) +} + +pub fn get_user_by_email(email: &str, hide_password: bool) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM users WHERE email = ?")?; + let row = stmt.query_row([email], |row| Ok(user_from_row(row, hide_password)?)).optional()?; + Ok(row) +} + +pub fn get_user_by_password(password: &str, hide_password: bool) -> Result, rusqlite::Error> { + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM users WHERE password = ?")?; + let row = stmt.query_row([password], |row| Ok(user_from_row(row, hide_password)?)).optional()?; + Ok(row) +} + +pub fn get_user_page(page: u64, hide_password: bool) -> Result, rusqlite::Error> { + let page_size = 5; + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM users ORDER BY user_id DESC LIMIT ? OFFSET ?")?; + let row = stmt.query_map([page_size, page_size * page], |row| Ok(user_from_row(row, hide_password)?))?; + Ok(row.into_iter().flatten().collect()) +} + +pub fn add_user(firstname: &str, lastname: &str, email: &str, password: &str, gender: &str, day: u8, month: u8, year: u32) -> Result { + let date = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64; + + let conn = database::connect()?; + let mut stmt = conn.prepare("INSERT INTO users (firstname, lastname, email, password, gender, date, day, month, year) VALUES(?,?,?,?,?,?,?,?,?) RETURNING *;")?; + let user = stmt.query_row((firstname, lastname, email, password, gender, date, day, month, year), |row| Ok(user_from_row(row, false)?))?; + Ok(user) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9ad772d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,48 @@ +use std::net::SocketAddr; +use axum::{Router, response::Response, http::Request, middleware::{Next, self}}; +use tower_cookies::CookieManagerLayer; +use types::response::ResponseCode; + +use crate::api::{pages, auth, users, posts}; + +mod api; +mod database; +mod types; + +async fn serve(req: Request, next: Next) -> Response { + let Ok(file) = ResponseCode::Success.file(&req.uri().to_string()).await else { + return next.run(req).await + }; + file +} + +async fn not_found() -> Response { + match ResponseCode::NotFound.file("/404.html").await { + Ok(file) => file, + Err(err) => err + } +} + +#[tokio::main] +async fn main() { + + database::init().unwrap(); + + let app = Router::new() + .fallback(not_found) + .layer(middleware::from_fn(serve)) + .nest("/", pages::router()) + .nest("/api/auth", auth::router()) + .nest("/api/users", users::router()) + .nest("/api/posts", posts::router()) + .layer(CookieManagerLayer::new()); + + let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); + println!("Listening on {}", addr); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + +} diff --git a/src/types/extract.rs b/src/types/extract.rs new file mode 100644 index 0000000..6518ca1 --- /dev/null +++ b/src/types/extract.rs @@ -0,0 +1,65 @@ +use std::io::Read; + +use axum::{extract::{FromRequestParts, FromRequest}, async_trait, response::Response, http::{request::Parts, Request}, TypedHeader, headers::Cookie, body::HttpBody, BoxError}; +use bytes::Bytes; +use serde::de::DeserializeOwned; + +use crate::types::{user::User, response::{ResponseCode, Result}, session::Session}; + +pub struct AuthorizedUser(pub User); + +#[async_trait] +impl FromRequestParts for AuthorizedUser where S: Send + Sync { + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + + let Ok(Some(cookies)) = Option::>::from_request_parts(parts, state).await else { + return Err(ResponseCode::Forbidden.msg("No cookies provided")) + }; + + let Some(token) = cookies.get("auth") else { + return Err(ResponseCode::Forbidden.msg("No auth token provided")) + }; + + let Ok(session) = Session::from_token(&token) else { + return Err(ResponseCode::Unauthorized.msg("Auth token invalid")) + }; + + let Ok(user) = User::from_user_id(session.user_id, true) else { + return Err(ResponseCode::InternalServerError.msg("Valid token but no valid user")) + }; + + Ok(AuthorizedUser(user)) + } +} + +pub struct Json(pub T); + +#[async_trait] +impl FromRequest for Json where + T: DeserializeOwned, + B: HttpBody + Send + 'static, + B::Data: Send, + B::Error: Into, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(req: Request, state: &S) -> Result { + + let Ok(bytes) = Bytes::from_request(req, state).await else { + return Err(ResponseCode::InternalServerError.msg("Failed to read request body")); + }; + + let Ok(string) = String::from_utf8(bytes.bytes().flatten().collect()) else { + return Err(ResponseCode::BadRequest.msg("Invalid utf8 body")) + }; + + let Ok(value) = serde_json::from_str(&string) else { + return Err(ResponseCode::BadRequest.msg("Invalid request body")) + }; + + Ok(Json(value)) + } +} \ No newline at end of file diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..089885e --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,5 @@ +pub mod user; +pub mod post; +pub mod session; +pub mod extract; +pub mod response; \ No newline at end of file diff --git a/src/types/post.rs b/src/types/post.rs new file mode 100644 index 0000000..94f0a9e --- /dev/null +++ b/src/types/post.rs @@ -0,0 +1,83 @@ +use std::collections::HashSet; +use serde::Serialize; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + +#[derive(Serialize)] +pub struct Post { + pub post_id: u64, + pub user_id: u64, + pub content: String, + pub likes: HashSet, + pub comments: Vec<(u64, String)>, + pub date: u64 +} + +impl Post { + + pub fn from_post_id(post_id: u64) -> Result { + let Ok(Some(post)) = database::posts::get_post(post_id) else { + return Err(ResponseCode::BadRequest.msg("Post does not exist")) + }; + + Ok(post) + } + + // pub fn from_post_ids(post_ids: Vec) -> Vec { + // post_ids.iter().map(|id| { + // let Ok(post) = Post::from_post_id(*id) else { + // return None; + // }; + // Some(post) + // }).flatten().collect() + // } + + pub fn from_post_page(page: u64) -> Result> { + let Ok(posts) = database::posts::get_post_page(page) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch posts")) + }; + Ok(posts) + } + + pub fn from_user_id(user_id: u64) -> Result> { + let Ok(posts) = database::posts::get_users_posts(user_id) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch posts")) + }; + Ok(posts) + } + + pub fn new(user_id: u64, content: String) -> Result { + let Ok(post) = database::posts::add_post(user_id, &content) else { + return Err(ResponseCode::InternalServerError.msg("Failed to create post")) + }; + + Ok(post) + } + + pub fn comment(&mut self, user_id: u64, content: String) -> Result<()> { + self.comments.push((user_id, content)); + + if database::posts::update_post(self.post_id, &self.likes, &self.comments).is_err() { + return Err(ResponseCode::InternalServerError.msg("Failed to comment on post")) + } + + Ok(()) + } + + pub fn like(&mut self, user_id: u64, state: bool) -> Result<()> { + + if state { + self.likes.insert(user_id); + } else { + self.likes.remove(&user_id); + } + + if database::posts::update_post(self.post_id, &self.likes, &self.comments).is_err() { + return Err(ResponseCode::InternalServerError.msg("Failed to comment on post")) + } + + Ok(()) + } + +} \ No newline at end of file diff --git a/src/types/response.rs b/src/types/response.rs new file mode 100644 index 0000000..bea3406 --- /dev/null +++ b/src/types/response.rs @@ -0,0 +1,60 @@ +use axum::{response::{IntoResponse, Response}, http::{StatusCode, Request, HeaderValue}, body::Body, headers::HeaderName}; +use tower::ServiceExt; +use tower_http::services::ServeFile; + +#[derive(Debug)] +pub enum ResponseCode { + Success, + Created, + BadRequest, + Unauthorized, + Forbidden, + NotFound, + ImATeapot, + InternalServerError +} + +impl ResponseCode { + pub fn code(self) -> StatusCode { + match self { + Self::Success => StatusCode::OK, + Self::Created => StatusCode::CREATED, + Self::BadRequest => StatusCode::BAD_REQUEST, + Self::Unauthorized => StatusCode::UNAUTHORIZED, + Self::Forbidden => StatusCode::FORBIDDEN, + Self::NotFound => StatusCode::NOT_FOUND, + Self::ImATeapot => StatusCode::IM_A_TEAPOT, + Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR + } + } + + pub fn msg(self, msg: &str) -> Response { + (self.code(), msg.to_owned()).into_response() + } + + pub fn json(self, json: &str) -> Response { + let mut res = (self.code(), json.to_owned()).into_response(); + res.headers_mut().insert( + HeaderName::from_static("content-type"), HeaderValue::from_static("application/json"), + ); + res + } + + pub async fn file(self, path: &str) -> Result { + if path.chars().position(|c| c == '.' ).is_none() { + return Err(ResponseCode::BadRequest.msg("Folders cannot be served")); + } + let path = format!("public{}", path); + let svc = ServeFile::new(path); + let Ok(mut res) = svc.oneshot(Request::new(Body::empty())).await else { + return Err(ResponseCode::InternalServerError.msg("Error wile fetching file")); + }; + if res.status() != StatusCode::OK { + return Err(ResponseCode::NotFound.msg("File not found")); + } + *res.status_mut() = self.code(); + Ok(res.into_response()) + } +} + +pub type Result = std::result::Result; \ No newline at end of file diff --git a/src/types/session.rs b/src/types/session.rs new file mode 100644 index 0000000..8064fb1 --- /dev/null +++ b/src/types/session.rs @@ -0,0 +1,38 @@ +use rand::{distributions::Alphanumeric, Rng}; +use serde::Serialize; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + +#[derive(Serialize)] +pub struct Session { + pub user_id: u64, + pub token: String +} + +impl Session { + + pub fn from_token(token: &str) -> Result { + let Ok(Some(session)) = database::sessions::get_session(token) else { + return Err(ResponseCode::BadRequest.msg("Invalid auth token")); + }; + + Ok(session) + } + + pub fn new(user_id: u64) -> Result { + let token: String = rand::thread_rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect(); + match database::sessions::set_session(user_id, &token) { + Err(_) => return Err(ResponseCode::BadRequest.msg("Failed to create session")), + Ok(_) => return Ok(Session {user_id, token}) + }; + } + + pub fn delete(user_id: u64) -> Result<()> { + if let Err(_) = database::sessions::delete_session(user_id) { + return Err(ResponseCode::InternalServerError.msg("Failed to logout")); + }; + Ok(()) + } + +} \ No newline at end of file diff --git a/src/types/user.rs b/src/types/user.rs new file mode 100644 index 0000000..1213a75 --- /dev/null +++ b/src/types/user.rs @@ -0,0 +1,79 @@ +use serde::{Serialize, Deserialize}; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + + +#[derive(Serialize, Deserialize, Debug)] +pub struct User { + pub user_id: u64, + pub firstname: String, + pub lastname: String, + pub email: String, + pub password: String, + pub gender: String, + pub date: u64, + pub day: u8, + pub month: u8, + pub year: u32, +} + +impl User { + + pub fn from_user_id(user_id: u64, hide_password: bool) -> Result { + let Ok(Some(user)) = database::users::get_user_by_id(user_id, hide_password) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn from_user_ids(user_ids: Vec) -> Vec { + user_ids.iter().map(|user_id| { + let Ok(Some(user)) = database::users::get_user_by_id(*user_id, true) else { + return None; + }; + Some(user) + }).flatten().collect() + } + + pub fn from_user_page(page: u64) -> Result> { + let Ok(users) = database::users::get_user_page(page, true) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch users")) + }; + Ok(users) + } + + pub fn from_email(email: &str) -> Result { + let Ok(Some(user)) = database::users::get_user_by_email(email, false) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn from_password(password: &str) -> Result { + let Ok(Some(user)) = database::users::get_user_by_password(password, true) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn new(firstname: String, lastname: String, email: String, password: String, gender: String, day: u8, month: u8, year: u32) -> Result { + if let Ok(_) = User::from_email(&email) { + return Err(ResponseCode::BadRequest.msg(&format!("Email is already in use by {}", &email))) + } + + if let Ok(user) = User::from_password(&password) { + return Err(ResponseCode::BadRequest.msg(&format!("Password is already in use by {}", user.email))) + } + + let Ok(user) = database::users::add_user(&firstname, &lastname, &email, &password, &gender, day, month, year) else { + return Err(ResponseCode::InternalServerError.msg("Failed to create new uesr")) + }; + + Ok(user) + } + +} \ No newline at end of file