diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..2a5d565 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,16 @@ +name: Security audit +on: + schedule: + - cron: "0 0 * * *" + push: + paths: + - "**/Cargo.toml" + - "**/Cargo.lock" +jobs: + security_audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: taiki-e/install-action@cargo-deny + - name: Scan for vulnerabilities + run: cargo deny check advisories diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..388235a --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,48 @@ +name: Generate changelog +# on: +# push: +# tags: +# - "v[0-9]+.[0-9]+.[0-9]+" +jobs: + changelog: + name: Generate changelog + runs-on: ubuntu-latest + outputs: + release_body: ${{ steps.git-cliff.outputs.content }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Generate a changelog + uses: orhun/git-cliff-action@v2 + id: git-cliff + with: + config: cliff.toml + args: -vv --latest --strip header + env: + OUTPUT: CHANGES.md + + # use release body in the same job + - name: Upload the binary releases + uses: svenstaro/upload-release-action@v2 + with: + file: binary_release.zip + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + body: ${{ steps.git-cliff.outputs.content }} + + # use release body in another job + upload: + name: Upload the release + needs: changelog + runs-on: ubuntu-latest + steps: + - name: Upload the binary releases + uses: svenstaro/upload-release-action@v2 + with: + file: binary_release.zip + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + body: ${{ needs.changelog.outputs.release_body }} diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml new file mode 100644 index 0000000..ca7a996 --- /dev/null +++ b/.github/workflows/general.yml @@ -0,0 +1,53 @@ +name: Rust + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run tests + run: cargo test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Enforce formatting + run: cargo fmt --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Linting + run: cargo clippy -- -D warnings + + coverage: + name: Code coverage + runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin + options: --security-opt seccomp=unconfined + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Generate code coverage + run: | + cargo tarpaulin --verbose --workspace diff --git a/Cargo.lock b/Cargo.lock index 5cae829..4c2ebd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,36 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aho-corasick" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - [[package]] name = "async-trait" version = "0.1.68" @@ -72,7 +42,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", + "bitflags", "bytes", "futures-util", "http", @@ -140,12 +110,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" - [[package]] name = "bumpalo" version = "3.13.0" @@ -170,75 +134,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets", -] - -[[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 = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" -dependencies = [ - "cookie 0.16.2", - "idna 0.2.3", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "cookie_store" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a18f35792056f8c7c2de9c002e7e4fe44c7b5f66e7d99f46468dbb730a7ea7" -dependencies = [ - "cookie 0.16.2", - "idna 0.3.0", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -255,41 +150,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "encoding_rs" version = "0.8.32" @@ -299,12 +159,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.1" @@ -380,17 +234,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.28" @@ -410,22 +253,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", - "futures-macro", "futures-task", "pin-project-lite", "pin-utils", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", ] [[package]] @@ -446,7 +276,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", @@ -459,30 +289,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "http" version = "0.2.9" @@ -505,34 +317,12 @@ dependencies = [ "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 = "httpc-test" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a425cb8352fb5080b3622e8a4265c63e75bedd68a4c19a83f7d4c88f9c9667" -dependencies = [ - "cookie 0.16.2", - "http", - "reqwest", - "reqwest_cookie_store", - "serde", - "serde_json", - "thiserror", - "tokio", -] - [[package]] name = "httpdate" version = "1.0.2" @@ -576,56 +366,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[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 = "idna" version = "0.4.0" @@ -643,19 +383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" -dependencies = [ - "equivalent", - "hashbrown 0.14.1", - "serde", + "hashbrown", ] [[package]] @@ -699,29 +427,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy-regex" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -732,19 +437,10 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "learn_axum" version = "0.1.0" dependencies = [ - "anyhow", - "async-trait", "axum", - "httpc-test", - "lazy-regex", - "serde", - "serde_json", - "serde_with", - "strum_macros", + "hyper", + "reqwest", "tokio", - "tower-cookies", - "tower-http", - "uuid", ] [[package]] @@ -775,12 +471,6 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.7.0" @@ -799,16 +489,6 @@ 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 = "miniz_oxide" version = "0.7.1" @@ -847,15 +527,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.16.0" @@ -887,7 +558,7 @@ version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -992,12 +663,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[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.63" @@ -1007,22 +672,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - [[package]] name = "quote" version = "1.0.29" @@ -1032,74 +681,15 @@ 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.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] -[[package]] -name = "regex" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - [[package]] name = "reqwest" version = "0.11.18" @@ -1108,8 +698,6 @@ checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64", "bytes", - "cookie 0.16.2", - "cookie_store 0.16.2", "encoding_rs", "futures-core", "futures-util", @@ -1139,19 +727,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "reqwest_cookie_store" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06b407c05de7a0f7e4cc2a56af5e9bd6468e509124e81078ce1f8bc2ed3536bf" -dependencies = [ - "bytes", - "cookie 0.16.2", - "cookie_store 0.19.1", - "reqwest", - "url", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1164,7 +739,7 @@ version = "0.37.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25693a73057a1b4cb56179dd3c7ea21a7c6c5ee7d85781f5749b46f34b79c" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", "io-lifetimes", "libc", @@ -1205,7 +780,7 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1227,20 +802,6 @@ name = "serde" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.164" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "serde_json" @@ -1274,35 +835,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.0.2", - "serde", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1347,25 +879,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "syn" version = "2.0.22" @@ -1397,53 +910,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[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", -] - -[[package]] -name = "time" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" -dependencies = [ - "time-core", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -1529,48 +995,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-cookies" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984" -dependencies = [ - "async-trait", - "axum-core", - "cookie 0.17.0", - "futures-util", - "http", - "parking_lot", - "pin-project-lite", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" -dependencies = [ - "bitflags 2.3.3", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "httpdate", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower-layer" version = "0.3.2" @@ -1610,15 +1034,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[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" @@ -1647,32 +1062,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna", "percent-encoding", ] -[[package]] -name = "uuid" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" -dependencies = [ - "getrandom", - "rand", -] - [[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.1" @@ -1786,15 +1185,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index 5956b28..240d499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,24 +3,31 @@ name = "learn_axum" version = "0.1.0" edition = "2021" +[lib] +path = "src/lib.rs" + +[[bin]] +path = "src/main.rs" +name = "learn_axum" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio = { version = "1.32.0", features = ["full"] } -# Serde / json -serde = { version = "1.0", features = ["derive"] } -serde_json = "1" -serde_with = "3" -# Axum +hyper = { version = "0.14.27", features = ["full"] } +# # Serde / json +# serde = { version = "1.0", features = ["derive"] } +# serde_json = "1" +# serde_with = "3" +# # Axum axum = { version = "0.6.20" } -tower-http = { version = "0.4.4", features = ["fs"] } -tower-cookies = "0.9" -# Others -lazy-regex = "3" -async-trait = "0.1" -strum_macros = "0.25" -uuid = { version = "1", features = ["v4", "fast-rng"] } +# tower-http = { version = "0.4.4", features = ["fs"] } +# tower-cookies = "0.9" +# # Others +# lazy-regex = "3" +# async-trait = "0.1" +# strum_macros = "0.25" +# uuid = { version = "1", features = ["v4", "fast-rng"] } [dev-dependencies] -anyhow = "1" -httpc-test = "0.1.5" +reqwest = "0.11" diff --git a/migrations/20220318150330_create_subscriptions_table.sql b/migrations/20220318150330_create_subscriptions_table.sql new file mode 100644 index 0000000..ad782aa --- /dev/null +++ b/migrations/20220318150330_create_subscriptions_table.sql @@ -0,0 +1,8 @@ +-- Create Subscriptions Table +CREATE TABLE subscriptions ( + id UUID NOT NULL, + PRIMARY KEY (id), + email TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + subscribed_at TIMESTAMPTZ NOT NULL +); diff --git a/scripts/init_db.sh b/scripts/init_db.sh new file mode 100755 index 0000000..3f37c0e --- /dev/null +++ b/scripts/init_db.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -x +set -eo pipefail + +if ! [ -x "$(command -v psql)" ]; then + echo >&2 "Error: psql is not installed." + exit 1 +fi + +if ! [ -x "$(command -v sqlx)" ]; then + echo >&2 "Error: sqlx is not installed." + echo >&2 "Use:" + echo >&2 " cargo install --version=0.5.7 sqlx-cli --no-default-features --features postgres" + echo >&2 "to install it." + exit 1 +fi + +DB_USER=${POSTGRES_USER:=postgres} +DB_PASSWORD="${POSTGRES_PASSWORD:=password}" +DB_NAME="${POSTGRES_DB:=newsletter}" +DB_PORT="${POSTGRES_PORT:=5432}" + +# Allow to skip container run if a containerized Postgres database is already running +if [[ -z "${SKIP_DB_RUN}" ]] +then + podman run \ + -e POSTGRES_USER=${DB_USER} \ + -e POSTGRES_PASSWORD=${DB_PASSWORD} \ + -e POSTGRES_DB=${DB_NAME} \ + -p "${DB_PORT}":5432 \ + -d postgres \ + postgres -N 1000 +fi + +until PGPASSWORD="${DB_PASSWORD}" psql -h "localhost" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do + >&2 echo "Postgres is still unavailable - sleeping" + sleep 1 +done + +>&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!" + +export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_NAME} +sqlx database create +sqlx migrate run + +>&2 echo "Postgres has been migrated, ready to go!" diff --git a/src/ctx.rs b/src/ctx.rs deleted file mode 100644 index 4cd72ef..0000000 --- a/src/ctx.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[derive(Debug, Clone)] -pub struct Ctx { - user_id: u64, -} - -// Constructor -impl Ctx { - pub fn new(user_id: u64) -> Self { - Self { user_id } - } -} - -// Property Accessors. -impl Ctx { - pub fn user_id(&self) -> u64 { - self.user_id - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index e414b15..0000000 --- a/src/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use serde::Serialize; - -pub type Result = core::result::Result; - -#[derive(Clone, Debug, Serialize, strum_macros::AsRefStr)] -#[serde(tag = "type", content = "data")] -pub enum Error { - LoginFail, - - // -- Auth errors. - AuthFailNoAuthTokenCookie, - AuthFailTokenWrongFormat, - AuthFailCtxNotInRequestExt, - - // -- Model errors. - PropertyDeleteFailIdNotFound { id: u64 }, -} - -impl Error { - pub fn client_status_and_error(&self) -> (StatusCode, ClientError) { - match self { - // -- Login. - Self::LoginFail => (StatusCode::UNAUTHORIZED, ClientError::LOGIN_FAIL), - - // -- Auth. - Self::AuthFailNoAuthTokenCookie - | Self::AuthFailTokenWrongFormat - | Self::AuthFailCtxNotInRequestExt => (StatusCode::FORBIDDEN, ClientError::NO_AUTH), - - // -- Model. - Self::PropertyDeleteFailIdNotFound { .. } => { - (StatusCode::BAD_REQUEST, ClientError::INVALID_PARAMS) - } - - // -- Fallback. - _ => ( - StatusCode::INTERNAL_SERVER_ERROR, - ClientError::SERVICE_ERROR, - ), - } - } -} - -impl IntoResponse for Error { - fn into_response(self) -> Response { - println!("->> {:<12} - {self:?}", "INTO_RESPONSE"); - - // Create a placeholder Axum response. - let mut response = StatusCode::INTERNAL_SERVER_ERROR.into_response(); - - // Insert the Error into the response. - response.extensions_mut().insert(self); - - response - } -} - -#[derive(Debug, strum_macros::AsRefStr)] -#[allow(non_camel_case_types)] -pub enum ClientError { - LOGIN_FAIL, - NO_AUTH, - INVALID_PARAMS, - SERVICE_ERROR, -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..37bcb68 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,24 @@ +#![allow(unused)] +use axum::extract::{Path, Query}; +use axum::http::{Method, Uri}; +use axum::response::{Html, IntoResponse, Response}; +use axum::routing::{get, get_service, IntoMakeService}; +use axum::Server; +use axum::{middleware, Json, Router}; +use hyper::server::conn::AddrIncoming; +use std::net::SocketAddr; +use std::net::TcpListener; + +pub type App = Server>; + +/// API routes +fn app() -> Router { + Router::new().route("/health_check", get(|| async {})) +} + +/// Start the server +pub fn run(listener: TcpListener) -> hyper::Result { + let app = app(); + let server = Server::from_tcp(listener)?.serve(app.into_make_service()); + Ok(server) +} diff --git a/src/log.rs b/src/log.rs deleted file mode 100644 index fce8fed..0000000 --- a/src/log.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::time::SystemTime; - -use crate::ctx::Ctx; -use crate::error::ClientError; -use crate::{Error, Result}; -use axum::http::{Method, Uri}; -use serde::Serialize; -use serde_json::{json, Value}; -use serde_with::skip_serializing_none; -use uuid::Uuid; - -pub async fn log_request( - uuid: Uuid, - req_method: Method, - uri: Uri, - ctx: Option, - service_error: Option<&Error>, - client_error: Option, -) -> Result<()> { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis(); - - let error_type = service_error.map(|se| se.as_ref().to_string()); - let error_data = serde_json::to_value(service_error) - .ok() - .and_then(|mut v| v.get_mut("data").map(|v| v.take())); - - // Create the RequestLogLine. - let log_line = RequestLogLine { - uuid: uuid.to_string(), - timestamp: timestamp.to_string(), - - req_path: uri.path().to_string(), - req_method: req_method.to_string(), - - user_id: ctx.map(|ctx| ctx.user_id()), - - client_error_type: client_error.map(|e| e.as_ref().to_string()), - - error_type, - error_data, - }; - - println!(" ->> log request: \n{}", json!(log_line)); - - // TODO: Send to cloud-watch or something. - - Ok(()) -} - -#[skip_serializing_none] -#[derive(Serialize)] -struct RequestLogLine { - uuid: String, // uuid string formatted - timestamp: String, // (should be iso8601) - - // -- User and context attributes. - user_id: Option, - - // -- http request attributes. - req_path: String, - req_method: String, - - // -- Errors attributes. - client_error_type: Option, - error_type: Option, - error_data: Option, -} diff --git a/src/main.rs b/src/main.rs index 8eded5f..fb1651f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,108 +1,9 @@ -#![allow(unused)] - -use crate::model::ModelController; - -use self::ctx::Ctx; -pub use self::error::{Error, Result}; -use self::log::log_request; - -use std::net::SocketAddr; - -use axum::extract::{Path, Query}; -use axum::http::{Method, Uri}; -use axum::response::{Html, IntoResponse, Response}; -use axum::routing::{get, get_service}; -use axum::Server; -use axum::{middleware, Json, Router}; -use serde::Deserialize; -use serde_json::json; -use tower_cookies::CookieManagerLayer; -use tower_http::services::ServeDir; -use uuid::Uuid; - -mod ctx; -mod error; -mod log; -mod model; -mod web; - -#[derive(Debug, Deserialize)] -struct HelloParams { - name: Option, -} +use learn_axum::run; +use std::net::TcpListener; #[tokio::main] -async fn main() -> Result<()> { - let mc = ModelController::new().await?; - - let routes_apis = web::routes_properties::routes(mc.clone()) - .route_layer(middleware::from_fn(web::mw_auth::mw_require_auth)); - - let routes_all = Router::new() - .merge(Router::new().route("/health_check", get(|| async {}))) - .merge(web::routes_login::routes()) - .nest("/api", routes_apis) - .layer(middleware::map_response(main_response_mapper)) - .layer(middleware::from_fn_with_state( - mc.clone(), - web::mw_auth::mw_ctx_resolver, - )) - .layer(CookieManagerLayer::new()) // must be above? the auth routes - .fallback_service(routes_static()); - - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - println!("->> Listening on {addr}\n"); - Server::bind(&addr) - .serve(routes_all.into_make_service()) - .await - .unwrap(); - - Ok(()) -} - -/// Map the response to add headers, etc. -/// -/// * `res`: the response to map -async fn main_response_mapper( - ctx: Option, - uri: Uri, - req_method: Method, - res: Response, -) -> Response { - println!("->> {:<12} - main_response_mapper", "RES_MAPPER"); - let uuid = Uuid::new_v4(); - - // -- Get the eventual response error. - let service_error = res.extensions().get::(); - let client_status_error = service_error.map(|se| se.client_status_and_error()); - - // -- If client error, build the new response - let error_response = client_status_error - .as_ref() - .map(|(status_code, client_error)| { - let client_error_body = json!({ - "error": { - "type": client_error.as_ref(), - "req_uuid": uuid.to_string(), - } - }); - - println!(" ->> client_error_body: {client_error_body}"); - - // Build the new response from the client error body. - (*status_code, Json(client_error_body)).into_response() - }); - - // -- Build and log the server log line. - let client_error = client_status_error.unzip().1; - log_request(uuid, req_method, uri, ctx, service_error, client_error).await; - - println!(); - error_response.unwrap_or(res) -} - -/// Serve static files -// FIXME: remove -fn routes_static() -> Router { - Router::new().nest_service("/", get_service(ServeDir::new("./"))) +async fn main() -> hyper::Result<()> { + let addr = format!("127.0.0.1:{}", "3000"); + let listener = TcpListener::bind(addr).expect("Unable to bind to port"); + run(listener)?.await } diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index 732043b..0000000 --- a/src/model.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Simplistic model layer -//! (with mock-store layer) - -use crate::{ctx::Ctx, Error, Result}; -use serde::{Deserialize, Serialize}; -use std::sync::{Arc, Mutex}; - -// region: --- Property Types -#[derive(Clone, Debug, Serialize)] -pub struct Property { - pub id: u64, - pub creator_id: u64, - pub address: String, - pub contact: String, -} - -#[derive(Deserialize)] -pub struct PropertyForCreate { - pub address: String, - pub contact: String, -} - -// endregion: --- Property Types - -// region: --- Model Controller - -#[derive(Clone)] -pub struct ModelController { - property_store: Arc>>>, -} - -// Constructor -impl ModelController { - pub async fn new() -> Result { - Ok(Self { - property_store: Arc::default(), - }) - } -} - -// CRUD implementation -impl ModelController { - pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result { - let mut store = self.property_store.lock().unwrap(); - let id = store.len() as u64; - let property = Property { - id, - creator_id: ctx.user_id(), - address: property.address, - contact: property.contact, - }; - store.push(Some(property.clone())); - Ok(property) - } - - pub async fn list_properties(&self, ctx: Ctx) -> Result> { - let store = self.property_store.lock().unwrap(); - let properties = store.iter().filter_map(|p| p.clone()).collect(); - Ok(properties) - } - - pub async fn delete_property(&self, ctx: Ctx, id: u64) -> Result { - let mut store = self.property_store.lock().unwrap(); - let property = store.get_mut(id as usize).and_then(|p| p.take()); - property.ok_or(Error::PropertyDeleteFailIdNotFound { id }) - } -} - -// endregion: --- Model Controller diff --git a/src/web/mod.rs b/src/web/mod.rs deleted file mode 100644 index e414648..0000000 --- a/src/web/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod mw_auth; -pub mod routes_login; -pub mod routes_properties; - -/// The cookie name for the auth token -pub const AUTH_TOKEN: &str = "auth-token"; diff --git a/src/web/mw_auth.rs b/src/web/mw_auth.rs deleted file mode 100644 index 9942434..0000000 --- a/src/web/mw_auth.rs +++ /dev/null @@ -1,93 +0,0 @@ -use async_trait::async_trait; -use axum::extract::FromRequestParts; -use axum::http::request::Parts; -use axum::http::Request; -use axum::middleware::Next; -use axum::response::Response; -use axum::RequestPartsExt; -use lazy_regex::regex_captures; -use tower_cookies::{Cookie, Cookies}; - -use crate::ctx::Ctx; -use crate::web::AUTH_TOKEN; -use crate::{Error, Result}; - -pub async fn mw_ctx_resolver( - cookies: Cookies, - mut req: Request, - next: Next, -) -> Result { - println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE"); - - let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); - - // Compute Result. - let result_ctx = match auth_token - .ok_or(Error::AuthFailNoAuthTokenCookie) - .and_then(parse_token) - { - Ok((user_id, _exp, _sign)) => { - // TODO: Token components validations. - Ok(Ctx::new(user_id)) - } - Err(e) => Err(e), - }; - - // Remove the cookie if something went wrong other than NoAuthTokenCookie. - if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) { - cookies.remove(Cookie::named(AUTH_TOKEN)) - } - - // Store the ctx_result in the request extension. - req.extensions_mut().insert(result_ctx); - - Ok(next.run(req).await) -} - -/// Middleware to require authentication. -pub async fn mw_require_auth( - ctx: Result, - req: Request, - next: Next, -) -> Result { - println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE"); - - ctx?; - - Ok(next.run(req).await) -} - -// region: --- Ctx Extractor - -#[async_trait] -impl FromRequestParts for Ctx { - type Rejection = Error; - - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - println!("->> {:<12} - Ctx", "EXTRACTOR"); - - parts - .extensions - .get::>() - .ok_or(Error::AuthFailCtxNotInRequestExt)? - .clone() - } -} - -// endregion: --- Ctx Extractor - -/// Parse a token of format `user-[user-id].[expiration].[signature]` -/// Returns (user-id, expiration, signature) -fn parse_token(token: String) -> Result<(u64, String, String)> { - let (_whole, user_id, exp, sign) = regex_captures!( - r#"^user-(\d+)\.(.+)\.(.+)"#, // a literal regex - &token - ) - .ok_or(Error::AuthFailTokenWrongFormat)?; - - let user_id: u64 = user_id - .parse() - .map_err(|_| Error::AuthFailTokenWrongFormat)?; - - Ok((user_id, exp.to_string(), sign.to_string())) -} diff --git a/src/web/routes_login.rs b/src/web/routes_login.rs deleted file mode 100644 index d7b42a5..0000000 --- a/src/web/routes_login.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::{web, Error, Result}; -use axum::routing::post; -use axum::{Json, Router}; -use serde::Deserialize; -use serde_json::{json, Value}; -use tower_cookies::{Cookie, Cookies}; - -pub fn routes() -> Router { - Router::new().route("/api/login", post(api_login)) -} - -async fn api_login(cookies: Cookies, payload: Json) -> Result> { - println!("->> {:<12} - api_login", "HANDLER"); - - if payload.username != "demo1" || payload.password != "demo1" { - return Err(Error::LoginFail); - } - - // FIXME: Implement real auth-token generation/signature. - cookies.add(Cookie::new(web::AUTH_TOKEN, "user-1.exp.sign")); - - let body = Json(json!({ - "result": { - "success": true - } - })); - - Ok(body) -} - -#[derive(Debug, Deserialize)] -struct LoginPayload { - username: String, - password: String, -} diff --git a/src/web/routes_properties.rs b/src/web/routes_properties.rs deleted file mode 100644 index 1270ba6..0000000 --- a/src/web/routes_properties.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::ctx::Ctx; -use crate::model::{ModelController, Property, PropertyForCreate}; -use crate::Result; -use axum::extract::{Path, State}; -use axum::routing::{delete, post}; -use axum::{Json, Router}; - -pub fn routes(mc: ModelController) -> Router { - Router::new() - .route("/properties", post(create_property).get(list_properties)) - .route("/properties/:id", delete(delete_property)) - .with_state(mc) -} - -// region: --- REST Handlers - -async fn create_property( - State(mc): State, - ctx: Ctx, - Json(property_fc): Json, -) -> Result> { - println!("->> {:<12} - create_property", "HANDLER"); - let property = mc.create_property(ctx, property_fc).await?; - Ok(Json(property)) -} - -async fn list_properties( - State(mc): State, - ctx: Ctx, -) -> Result>> { - println!("->> {:<12} - list_properties", "HANDLER"); - let properties = mc.list_properties(ctx).await?; - Ok(Json(properties)) -} - -async fn delete_property( - State(mc): State, - ctx: Ctx, - Path(id): Path, -) -> Result> { - println!("->> {:<12} - delete_property", "HANDLER"); - let property = mc.delete_property(ctx, id).await?; - Ok(Json(property)) -} - -// endregion: --- REST Handlers diff --git a/tests/health_check.rs b/tests/health_check.rs new file mode 100644 index 0000000..d9de26b --- /dev/null +++ b/tests/health_check.rs @@ -0,0 +1,34 @@ +use std::net::{SocketAddr, TcpListener}; + +struct TestApp { + addr: SocketAddr, +} + +#[tokio::test] +async fn health_check_works() { + let TestApp { addr, .. } = spawn_app().await; + + let client = reqwest::Client::new(); + let response = client + .get(format!("http://{addr}/health_check")) + .send() + .await + .expect("Failed to execute request."); + assert!(response.status().is_success()); + assert_eq!(Some(0), response.content_length()); +} + +// fn spawn_app() { +// let server = learn_axum::run().expect("Failed to bind address."); +// tokio::spawn(server); +// } +async fn spawn_app() -> TestApp { + let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); + let addr = listener.local_addr().unwrap(); + + let server = learn_axum::run(listener).expect("Failed to bind to address"); + + tokio::spawn(server); + + TestApp { addr } +} diff --git a/tests/quick_dev.rs b/tests/quick_dev.rs deleted file mode 100644 index 30017d2..0000000 --- a/tests/quick_dev.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![allow(unused_imports)] - -use anyhow::Result; -use serde_json::json; - -#[tokio::test] -async fn test_quick_dev() -> Result<()> { - let hc = httpc_test::new_client("http://localhost:3000")?; - hc.do_get("/hello?name=jen").await?.print().await?; - - let hc = httpc_test::new_client("http://localhost:3000")?; - hc.do_get("/hello2/mike").await?.print().await?; - - hc.do_get("/src/main.rs").await?.print().await?; - - hc.do_get("/src/blub.rs").await?.print().await?; - - let req_login = hc.do_post( - "/api/login", - json!( - { - "username": "demo1", - "password": "demowrong" - } - ), - ); - req_login.await?.print().await?; - let req_login = hc.do_post( - "/api/login", - json!( - { - "username": "demo1", - "password": "demo1" - } - ), - ); - req_login.await?.print().await?; - - hc.do_get("/hello2/mike").await?.print().await?; - - let req_create_property = hc.do_post( - "/api/properties", - json!( - { - "address": "Lolilat Street 1", - "contact": "01234 567890" - } - ), - ); - req_create_property.await?.print().await?; - let req_create_property = hc.do_post( - "/api/properties", - json!( - { - "address": "Lolilat Street 2", - "contact": "01243 217890" - } - ), - ); - req_create_property.await?.print().await?; - let req_get_properties = hc.do_get("/api/properties").await?; - req_get_properties.print().await?; - let req_delete_property = hc.do_delete("/api/properties/1").await?; - req_delete_property.print().await?; - let req_get_properties = hc.do_get("/api/properties").await?; - req_get_properties.print().await?; - let req_delete_property = hc.do_delete("/api/properties/0").await?; - req_delete_property.print().await?; - - Ok(()) -}