diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7e324eb0..8788bb70 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,7 +2,7 @@ name: Rust on: pull_request: - branches: [main, "renovate/*"] + branches: [main, "renovate/*", "CHAOS-224-KHAOS-rewrite"] push: branches: ["renovate/*"] @@ -12,11 +12,20 @@ env: jobs: build: runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: chaos + ports: + - 5432:5432 steps: - uses: actions/checkout@v4 - - name: Mock an env file + - name: Setup env file run: | - echo "DATABASE_URL=test_url" >> backend/.env + echo "DATABASE_URL=postgres://postgres:password@localhost:5432/chaos" >> backend/.env echo "JWT_SECRET=test_secret" >> backend/.env echo "GOOGLE_CLIENT_ID=test" >> backend/.env echo "GOOGLE_CLIENT_SECRET=test" >> backend/.env @@ -28,18 +37,29 @@ jobs: with: profile: minimal toolchain: stable - - uses: Swatinem/rust-cache@v2 + - name: Setup cargo cache + uses: actions/cache@v3 with: - working-directory: backend/ - - name: Build - run: cargo build --manifest-path backend/server/Cargo.toml - - name: Cargo Clippy - run: cargo clippy --manifest-path backend/server/Cargo.toml - - name: RustFmt - run: cargo fmt --manifest-path backend/server/Cargo.toml + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + backend/server/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + - name: Migrate DB + working-directory: backend + run: | + which sqlx || cargo install sqlx-cli --no-default-features --features native-tls,postgres + sqlx database create + sqlx migrate run - name: Build - run: cargo build --manifest-path backend/seed_data/Cargo.toml + working-directory: backend/server + run: cargo build - name: Cargo Clippy - run: cargo clippy --manifest-path backend/seed_data/Cargo.toml + working-directory: backend/server + run: cargo clippy - name: RustFmt - run: cargo fmt --manifest-path backend/seed_data/Cargo.toml + working-directory: backend/server + run: cargo fmt diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index 84f023f1..00000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.env -target -Dockerfile* \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index e9480c46..5d9dc7ef 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,5 @@ .env target -images +Cargo.lock +prisma-cli/prisma/migrations +/.idea \ No newline at end of file diff --git a/backend/Cargo.lock b/backend/Cargo.lock deleted file mode 100644 index 32ab2908..00000000 --- a/backend/Cargo.lock +++ /dev/null @@ -1,2873 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aead" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[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 = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "async-trait" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - -[[package]] -name = "bit_field" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" - -[[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 = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - -[[package]] -name = "bytemuck" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cc" -version = "1.0.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "serde", - "time 0.1.44", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "cookie" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" -dependencies = [ - "aes-gcm", - "base64 0.13.1", - "hkdf", - "hmac", - "percent-encoding", - "rand", - "sha2", - "subtle", - "time 0.3.17", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "cxx" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.103", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "devise" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" -dependencies = [ - "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "diesel" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" -dependencies = [ - "bitflags", - "byteorder", - "chrono", - "diesel_derives", - "pq-sys", - "r2d2", -] - -[[package]] -name = "diesel-derive-enum" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8910921b014e2af16298f006de12aa08af894b71f0f49a486ab6d74b17bbed" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "diesel_derives" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "diesel_migrations" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" -dependencies = [ - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "digest" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dotenv_codegen" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56966279c10e4f8ee8c22123a15ed74e7c8150b658b26c619c53f4a56eb4a8aa" -dependencies = [ - "dotenv_codegen_implementation", - "proc-macro-hack", -] - -[[package]] -name = "dotenv_codegen_implementation" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e737a3522cd45f6adc19b644ce43ef53e1e9045f2d2de425c1f468abd4cf33" -dependencies = [ - "dotenv", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "exr" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide 0.6.2", - "smallvec", - "threadpool", -] - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "figment" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" -dependencies = [ - "atomic", - "pear", - "serde", - "serde_json", - "toml", - "uncased", - "version_check", -] - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide 0.5.4", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin 0.9.4", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - -[[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-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generator" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", -] - -[[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", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "h2" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hkdf" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.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 = "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", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.24.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "indexmap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" -dependencies = [ - "autocfg", - "hashbrown", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" - -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" -dependencies = [ - "rayon", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.2", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "libc" -version = "0.2.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" - -[[package]] -name = "libwebp-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf" -dependencies = [ - "cc", -] - -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - -[[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 = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "migrations_internals" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860" -dependencies = [ - "diesel", -] - -[[package]] -name = "migrations_macros" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", -] - -[[package]] -name = "multer" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.4", - "tokio", - "tokio-util", - "version_check", -] - -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "openssl" -version = "0.10.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.42.0", -] - -[[package]] -name = "pear" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "pem" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" -dependencies = [ - "base64 0.13.1", -] - -[[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 1.0.103", -] - -[[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 = "png" -version = "0.17.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" -dependencies = [ - "bitflags", - "crc32fast", - "flate2", - "miniz_oxide 0.5.4", -] - -[[package]] -name = "polyval" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "pq-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b845d6d8ec554f972a2c5298aad68953fd64e7441e846075450b44656a016d1" -dependencies = [ - "vcpkg", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", - "version_check", - "yansi", -] - -[[package]] -name = "quote" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r2d2" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", -] - -[[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 = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "regex" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reqwest" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" -dependencies = [ - "base64 0.13.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rocket" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" -dependencies = [ - "async-stream", - "async-trait", - "atomic", - "atty", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time 0.3.17", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn 1.0.103", - "unicode-xid", -] - -[[package]] -name = "rocket_cors" -version = "0.6.0-alpha1" -source = "git+https://github.com/lawliet89/rocket_cors?branch=master#c17e8145baa4790319fdb6a473e465b960f55e7c" -dependencies = [ - "http", - "log", - "regex", - "rocket", - "serde", - "serde_derive", - "unicase", - "unicase_serde", - "url", -] - -[[package]] -name = "rocket_http" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" -dependencies = [ - "cookie", - "either", - "futures", - "http", - "hyper", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time 0.3.17", - "tokio", - "uncased", -] - -[[package]] -name = "rocket_sync_db_pools" -version = "0.1.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa48b6ab25013e9812f1b0c592741900b3a2a83c0936292e0565c0ac842f558" -dependencies = [ - "diesel", - "r2d2", - "rocket", - "rocket_sync_db_pools_codegen", - "serde", - "tokio", -] - -[[package]] -name = "rocket_sync_db_pools_codegen" -version = "0.1.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280ef2d232923e69cb93da156972eb5476a7cce5ba44843f6608f46a4abf7aab" -dependencies = [ - "devise", - "quote", -] - -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] - -[[package]] -name = "scheduled-thread-pool" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - -[[package]] -name = "security-framework" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "seed_data" -version = "0.1.0" -dependencies = [ - "chrono", - "diesel", - "dotenv", - "server", -] - -[[package]] -name = "serde" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "serde_json" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" -dependencies = [ - "itoa", - "ryu", - "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 = "server" -version = "0.1.0" -dependencies = [ - "chrono", - "diesel", - "diesel-derive-enum", - "diesel_migrations", - "dotenv", - "dotenv_codegen", - "figment", - "image", - "itertools", - "jsonwebtoken", - "once_cell", - "reqwest", - "rocket", - "rocket_cors", - "rocket_sync_db_pools", - "serde", - "serde_json", - "strum", - "uuid", - "webp", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time 0.3.17", -] - -[[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 = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.103", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tiff" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[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 = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[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", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[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", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "ubyte" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" -dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "serde", - "version_check", -] - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicase_serde" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef53697679d874d69f3160af80bc28de12730a985d57bdf2b47456ccb8b11f1" -dependencies = [ - "serde", - "unicase", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" -dependencies = [ - "getrandom", - "rand", - "uuid-macro-internal", -] - -[[package]] -name = "uuid-macro-internal" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f67b459f42af2e6e1ee213cb9da4dbd022d3320788c3fb3e1b893093f1e45da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "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.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webp" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae" -dependencies = [ - "image", - "libwebp-sys", -] - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[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 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/backend/Cargo.toml b/backend/Cargo.toml deleted file mode 100644 index b0dc9572..00000000 --- a/backend/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[workspace] - -members = [ - "seed_data", - "server" -] diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 4b5276b7..00000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM lukemathwalker/cargo-chef:latest-rust-1.68.0 AS chef -WORKDIR /app - -FROM chef AS planner -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - -FROM chef AS builder -COPY --from=planner /app/recipe.json recipe.json -RUN cargo chef cook --release --recipe-path recipe.json -COPY . . -RUN cargo build --release --bin server - -FROM ubuntu -RUN apt update -RUN apt install -y wget libpq5 -RUN wget https://mirrors.edge.kernel.org/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_amd64.deb \ - && dpkg -i libssl1.1_1.1.0g-2ubuntu4_amd64.deb \ - && rm libssl1.1_1.1.0g-2ubuntu4_amd64.deb - -COPY --from=builder /app/target/release/server / -CMD ["./server"] diff --git a/backend/README.md b/backend/README.md index 42919709..ae44afe5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,25 +1,64 @@ -# Setup guide - -* Install the latest (stable) Rust toolchain with [rustup](https://rustup.rs/) -* Install postgres with associated dev tools, eg. `sudo apt install postgresql-all` (something like `brew install postgresql` for osx) -* Download and save the backend `.env` file from vault to the root of the backend folder. -* Setup postgres accordingly - * Make sure the user is named `postgres` and the password is the same as the one in vault `.env` file - * the password is in the `DATABSE_URL` field, which will look very similar to `postgres://postgres:@localhost/chaos` -* Install diesel-cli with `cargo install diesel_cli --no-default-features --features postgres` -* Run `diesel setup && diesel migration run` - * If you get a server connection error, check out [this stackoverflow thread](https://stackoverflow.com/questions/32439167/psql-could-not-connect-to-server-connection-refused-error-when-connecting-to) - * this might delete code in the backend, make sure to restore any deletions. -* Run the server with `cargo run --bin server` - * If you want to run the server and get it to restart upon changes, you can install `cargo watch` with `cargo install cargo-watch` - * Then, run `cargo watch -x 'run --bin server'` instead - * It will watch files and continually re-compile upon changes -* If there are errrors, ask on discord. - - -# Scripts - -Scripts should be run from the `chaos/backend` directory: - * `scripts/become_super_user`- will prompt for the email address to turn into a GLOBAL super user - * `scripts/seed.sh` - will wipe your database and add some dummy data - * If you are getting a `bad variable nameread answer` error, ensure the file has LF-style newlines +# CHAOS Backend + +CHAOS' backend is implemented in Rust and for data persistence, we use PostgreSQL. + +## Table of Contents + +- [Dev Setup](#dev-setup) +- [Code Structure](#code-structure) +- [Tech Stack](#tech-stack) + + +## Dev Setup + +To run the backend in a dev/testing environment: +1. Install `docker-compose` (see [official installation guide](https://docs.docker.com/compose/install/)). +2. Navigate to the directory this file is in (`backend`) in your terminal (not `backend/server`). +3. Possibly terminate any running instances of postgres, as the dockerized postgres we will spawn uses the same default port, so the two might interefere with each other. +4. Run `./setup-dev-env.sh` (you might have to make it executable before with `chmod +x setup-dev-env.sh`), which should drop you into a new shell that has the required tools installed. +5. Now, you can `cd server` and should be able to `cargo build` successfully. +6. Once you exit out of the newly created shell (e.g. type `exit`, or kill the terminal), the dockerized postgres instance should automatically be torn down, so it's not unnecessarily running in the background all the time. + + +## Code Structure + +### Service +The service module contains all functions that conduct business logic, and also interact with the database. This +separation from the request handling makes it easy to swap out any new form of requests, but reuse the same business +logic. + +### Handler +The handler module takes care of request handling. It implements the framework or library we are using, invokes the +service functions and responds via HTTP with their return values. + +### Middleware +The middleware module contains middlewares, functions that run before or after the function handlers. A common use case +is authorization, where middleware is used to find the userId from the user's token. + +### Models +Models are Rust structs that represent the data. There must be a struct for each table in the database, as well as a +struct to describe the fully joined data. E.g. A campaign struct with a array of questions, even though questions are +stored as rows in a separate table. These models are used by the service functions when interacting with the database, +and also when conducting business logic. + +#### Request Path +Request -> Middleware (optional) -> Handler -> Service -> Middleware (Optional) -> Response + + +## Tech Stack + +### Web Server +- [Axum](https://github.com/tokio-rs/axum) + +### Persistence +- [SQLx](https://github.com/launchbadge/sqlx) - Queries and Migrations +- PostgreSQL + +### AuthN +- OAuth 2 (Google) + +### AuthZ +- JWT + +### Storage +- Object storage diff --git a/backend/api.json b/backend/api.json new file mode 100644 index 00000000..8be962fe --- /dev/null +++ b/backend/api.json @@ -0,0 +1,1296 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Chaos API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://chaos.csesoc.app/api", + "description": "Production server" + }, + { + "url": "http://localhost:3000/api", + "description": "Local server" + } + ], + "paths": { + "/auth/logout": { + "post": { + "operationId": "logout", + "description": "Invalidates current token.", + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "messages": { + "type": "string", + "example": "Successfully logged out." + } + } + } + } + } + } + } + } + }, + "/user": { + "get": { + "operationId": "getLoggedInUser", + "description": "Returns info about currently logged in user.", + "tags": [ + "User" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1541815603606036500 + }, + "email": { + "type": "string", + "example": "me@example.com" + }, + "zid": { + "type": "string", + "example": "z5555555" + }, + "name": { + "type": "string", + "example": "Clancy Lion" + }, + "degree_name": { + "type": "string", + "example": "Computer Science" + }, + "degree_starting_year": { + "type": "integer", + "example": 2024 + }, + "role": { + "type": "string", + "example": "User" + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "deleteUserById", + "description": "Deletes currently logged in user.", + "tags": [ + "User" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully deleted user." + } + } + } + } + } + }, + "401": { + "description": "Not logged.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + }, + "403": { + "description": "User is only admin of an organisation.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Cannot delete sole admin of an organisation." + } + } + } + } + } + } + } + } + }, + "/user/{id}": { + "get": { + "operationId": "getUserById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Returns info about specified user.", + "tags": [ + "User" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1541815603606036500 + }, + "email": { + "type": "string", + "example": "me@example.com" + }, + "zid": { + "type": "string", + "example": "z5555555" + }, + "name": { + "type": "string", + "example": "Clancy Lion" + }, + "degree_name": { + "type": "string", + "example": "Computer Science" + }, + "degree_starting_year": { + "type": "integer", + "example": 2024 + } + } + } + } + } + }, + "403": { + "description": "Requested user has not applied to one of authorized user's campaign.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Insufficient permissions" + } + } + } + } + } + } + } + } + }, + "/user/name": { + "patch": { + "operationId": "updateUserName", + "description": "Updates currently logged in user's name.", + "tags": [ + "User" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "example": "Clancy Tiger" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully updated name." + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + } + } + } + }, + "/user/zid": { + "patch": { + "operationId": "updateUserZid", + "description": "Updates currently logged in user's zID.", + "tags": [ + "User" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "zid": { + "type": "string", + "example": "z5123456" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully updated zID." + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + } + } + } + }, + "/user/degree": { + "patch": { + "operationId": "updateUserDegree", + "description": "Updates currently logged in user's degree.", + "tags": [ + "User" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "degree_name": { + "type": "string", + "example": "Electrical Engineering" + }, + "degree_starting_year": { + "type": "integer", + "example": 2024 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully updated email." + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + } + } + } + }, + "/organisation": { + "post": { + "operationId": "createOrganisation", + "description": "Creates a new organisation.", + "tags": [ + "Organisation" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "example": "UNSW Software Development Society" + }, + "admin": { + "type": "integer", + "format": "int64", + "example": 1541815603606036500, + "description": "User ID of admin" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully created organisation." + } + } + } + } + } + }, + "403": { + "description": "User is not a super user.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/organisation/{id}": { + "get": { + "operationId": "getOrganisationById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Returns info about specified organisation.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 6996987893965263000 + }, + "name": { + "type": "string", + "example": "UNSW Software Development Society" + }, + "logo": { + "type": "string", + "example": "76718252-2a13-4de2-bc07-f977c75dc52b" + }, + "created_at": { + "type": "string", + "example": "2024-02-10T18:25:43.511Z" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "deleteOrganisationById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Deletes specified organisation.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully deleted organisation." + } + } + } + } + } + }, + "403": { + "description": "User is not a super user.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/organisation/{id}/campaigns": { + "get": { + "operationId": "getOrganisationCampaignsById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Returns active campaigns for specified organisation.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "campaigns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 6996987893965263000 + }, + "name": { + "type": "string", + "example": "2024 Subcommittee Recruitment" + }, + "cover_image": { + "type": "string", + "example": "2d19617b-46fd-4927-9f53-77d69232ba5d" + }, + "description": { + "type": "string", + "example": "Are you excited to make a difference?" + }, + "starts_at": { + "type": "string", + "example": "2024-03-15T18:25:43.511Z" + }, + "ends_at": { + "type": "string", + "example": "2024-04-15T18:25:43.511Z" + } + } + } + } + } + } + } + } + } + } + } + }, + "/organisation/{id}/logo": { + "patch": { + "operationId": "updateOrganisationLogoById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Updates logo for specified organistion.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "upload_url": { + "type": "string", + "description": "Presigned S3 url to upload file.", + "example": "https://presignedurldemo.s3.eu-west-2.amazonaws.com/6996987893965262849/2d19617b-46fd-4927-9f53-77d69232ba5d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJJWZ7B6WCRGMKFGQ%2F20180210%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20180210T171315Z&X-Amz-Expires=1800&X-Amz-Signature=12b74b0788aa036bc7c3d03b3f20c61f1f91cc9ad8873e3314255dc479a25351&X-Amz-SignedHeaders=host" + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + }, + "403": { + "description": "User is not an organisation admin.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/organisation/{id}/members": { + "get": { + "operationId": "getOrganisationMembersById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Returns list of members of specified organisation.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "members": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1541815603606036500 + }, + "name": { + "type": "string", + "example": "Clancy Lion" + }, + "role": { + "type": "string", + "example": "Admin" + } + } + } + } + } + } + } + } + }, + "403": { + "description": "User is not an organisation admin or member.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "updateOrganisationMembersById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "members": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "integer", + "format": "int64" + }, + "example": [ + 1541815603606036500, + 1541815603606036700, + 1541815287306036500 + ] + } + } + } + } + } + }, + "description": "Specifies members for specified organistion.", + "tags": [ + "Organisation" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully updated members." + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + }, + "403": { + "description": "User is not an organisation admin.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/organisation/{id}/campaign": { + "post": { + "operationId": "createCampaign", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Organisation ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Creates a new campaign inside specified organisation.", + "tags": [ + "Organisation" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "example": "2024 Subcommittee Recruitment" + }, + "description": { + "type": "string", + "example": "Are you excited to make a difference?" + }, + "starts_at": { + "type": "string", + "example": "2024-03-15T18:25:43.511Z" + }, + "ends_at": { + "type": "string", + "example": "2024-04-15T18:25:43.511Z" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully created campaign." + } + } + } + } + } + }, + "403": { + "description": "User is not an admin of specified organisation.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/campaign": { + "get": { + "operationId": "getAllCampaigns", + "description": "Returns all active campaigns.", + "tags": [ + "Campaign" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "campaigns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 6996987893965263000 + }, + "organisation_id": { + "type": "integer", + "format": "int64", + "example": 1541815603606036700 + }, + "organisation_name": { + "type": "string", + "example": "UNSW Software Development Society" + }, + "name": { + "type": "string", + "example": "2024 Subcommittee Recruitment" + }, + "cover_image": { + "type": "string", + "example": "2d19617b-46fd-4927-9f53-77d69232ba5d" + }, + "description": { + "type": "string", + "example": "Are you excited to make a difference?" + }, + "starts_at": { + "type": "string", + "example": "2024-03-15T18:25:43.511Z" + }, + "ends_at": { + "type": "string", + "example": "2024-04-15T18:25:43.511Z" + } + } + } + } + } + } + } + } + } + } + } + }, + "/campaign/{id}": { + "get": { + "operationId": "getCampaignById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Campaign ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Returns info about specified campaign.", + "tags": [ + "Campaign" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 6996987893965263000 + }, + "organisation_id": { + "type": "integer", + "format": "int64", + "example": 1541815603606036700 + }, + "organisation_name": { + "type": "string", + "example": "UNSW Software Development Society" + }, + "name": { + "type": "string", + "example": "2024 Subcommittee Recruitment" + }, + "cover_image": { + "type": "string", + "example": "2d19617b-46fd-4927-9f53-77d69232ba5d" + }, + "description": { + "type": "string", + "example": "Are you excited to make a difference?" + }, + "starts_at": { + "type": "string", + "example": "2024-03-15T18:25:43.511Z" + }, + "ends_at": { + "type": "string", + "example": "2024-04-15T18:25:43.511Z" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "updateCampaignById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Campaign ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "example": "2024 Subcommittee Recruitment" + }, + "description": { + "type": "string", + "example": "Are you excited to make a difference?" + }, + "starts_at": { + "type": "string", + "example": "2024-03-15T18:25:43.511Z" + }, + "ends_at": { + "type": "string", + "example": "2024-04-15T18:25:43.511Z" + } + } + } + } + } + }, + "description": "Updates details of specified campaign.", + "tags": [ + "Campaign" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully updated campaign." + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + }, + "403": { + "description": "User is not an organisation admin.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "deleteCampaignById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Campaign ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Deletes specified campaign.", + "tags": [ + "Campaign" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Successfully deleted campaign." + } + } + } + } + } + }, + "403": { + "description": "User is not an admin of campaign's organisation.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + }, + "/campaign/{id}/banner": { + "patch": { + "operationId": "updateCampaignBannerById", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Campaign ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "description": "Updates banner image for specified campaign.", + "tags": [ + "Campaign" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "upload_url": { + "type": "string", + "description": "Presigned S3 url to upload file.", + "example": "https://presignedurldemo.s3.eu-west-2.amazonaws.com/6996987893965262849/2d19617b-46fd-4927-9f53-77d69232ba5d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJJWZ7B6WCRGMKFGQ%2F20180210%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20180210T171315Z&X-Amz-Expires=1800&X-Amz-Signature=12b74b0788aa036bc7c3d03b3f20c61f1f91cc9ad8873e3314255dc479a25351&X-Amz-SignedHeaders=host" + } + } + } + } + } + }, + "401": { + "description": "Not logged in.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Not logged in." + } + } + } + } + } + }, + "403": { + "description": "User is not an organisation admin.", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "example": "Unauthorized" + } + } + } + } + } + } + } + } + } + } + } \ No newline at end of file diff --git a/backend/api.yaml b/backend/api.yaml new file mode 100644 index 00000000..8b3f9ef2 --- /dev/null +++ b/backend/api.yaml @@ -0,0 +1,1061 @@ +openapi: "3.0.0" +info: + title: Chaos API + version: 1.0.0 +servers: + - url: https://chaos.csesoc.app/api + description: Production server + - url: http://localhost:3000/api + description: Local server + +paths: + /auth/logout: + post: + operationId: logout + description: Invalidates current token. + tags: + - Auth + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + messages: + type: string + example: Successfully logged out. + /user: + get: + operationId: getLoggedInUser + description: Returns info about currently logged in user. + tags: + - User + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + id: + type: integer + format: int64 + example: 1541815603606036480 + email: + type: string + example: me@example.com + zid: + type: string + example: z5555555 + name: + type: string + example: Clancy Lion + degree_name: + type: string + example: Computer Science + degree_starting_year: + type: integer + example: 2024 + role: + type: string + example: User + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + delete: + operationId: deleteUserById + description: Deletes currently logged in user. + tags: + - User + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully deleted user. + '403': + description: User is only admin of an organisation. + content: + application/json: + schema: + properties: + error: + type: string + example: "Cannot delete sole admin of an organisation." + '401': + description: Not logged. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + /user/{id}: + get: + operationId: getUserById + parameters: + - name: id + in: path + description: User ID + required: true + schema: + type: integer + format: int64 + description: Returns info about specified user. + tags: + - User + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + id: + type: integer + format: int64 + example: 1541815603606036480 + email: + type: string + example: me@example.com + zid: + type: string + example: z5555555 + name: + type: string + example: Clancy Lion + degree_name: + type: string + example: Computer Science + degree_starting_year: + type: integer + example: 2024 + '403': + description: Requested user has not applied to one of authorized user's campaign. + content: + application/json: + schema: + properties: + error: + type: string + example: "Insufficient permissions" + /user/name: + patch: + operationId: updateUserName + description: Updates currently logged in user's name. + tags: + - User + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: "Clancy Tiger" + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully updated name. + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + /user/zid: + patch: + operationId: updateUserZid + description: Updates currently logged in user's zID. + tags: + - User + requestBody: + required: true + content: + application/json: + schema: + properties: + zid: + type: string + example: "z5123456" + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully updated zID. + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + /user/degree: + patch: + operationId: updateUserDegree + description: Updates currently logged in user's degree. + tags: + - User + requestBody: + required: true + content: + application/json: + schema: + properties: + degree_name: + type: string + example: "Electrical Engineering" + degree_starting_year: + type: integer + example: 2024 + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully updated email. + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + /organisation: + post: + operationId: createOrganisation + description: Creates a new organisation. + tags: + - Organisation + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: "UNSW Software Development Society" + admin: + type: integer + format: int64 + example: 1541815603606036480 + description: User ID of admin + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully created organisation. + '403': + description: User is not a super user. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /organisation/{id}: + get: + operationId: getOrganisationById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Returns info about specified organisation. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + id: + type: integer + format: int64 + example: 6996987893965262849 + name: + type: string + example: UNSW Software Development Society + logo: + type: string + example: "76718252-2a13-4de2-bc07-f977c75dc52b" + created_at: + type: string + example: 2024-02-10T18:25:43.511Z + delete: + operationId: deleteOrganisationById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Deletes specified organisation. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully deleted organisation. + '403': + description: User is not a super user. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /organisation/{id}/campaigns: + get: + operationId: getOrganisationCampaignsById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Returns active campaigns for specified organisation. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + campaigns: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + example: 6996987893965262849 + name: + type: string + example: 2024 Subcommittee Recruitment + cover_image: + type: string + example: 2d19617b-46fd-4927-9f53-77d69232ba5d + description: + type: string + example: Are you excited to make a difference? + starts_at: + type: string + example: 2024-03-15T18:25:43.511Z + ends_at: + type: string + example: 2024-04-15T18:25:43.511Z + /organisation/{id}/logo: + patch: + operationId: updateOrganisationLogoById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Updates logo for specified organistion. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + upload_url: + type: string + description: Presigned S3 url to upload file. + example: https://presignedurldemo.s3.eu-west-2.amazonaws.com/6996987893965262849/2d19617b-46fd-4927-9f53-77d69232ba5d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJJWZ7B6WCRGMKFGQ%2F20180210%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20180210T171315Z&X-Amz-Expires=1800&X-Amz-Signature=12b74b0788aa036bc7c3d03b3f20c61f1f91cc9ad8873e3314255dc479a25351&X-Amz-SignedHeaders=host + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + '403': + description: User is not an organisation admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /organisation/{id}/members: + get: + operationId: getOrganisationMembersById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Returns list of members of specified organisation. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + members: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + example: 1541815603606036480 + name: + type: string + example: Clancy Lion + role: + type: string + example: Admin + '403': + description: User is not an organisation admin or member. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + put: + operationId: updateOrganisationMembersById + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + requestBody: + required: true + content: + application/json: + schema: + properties: + members: + type: array + uniqueItems: true + items: + type: integer + format: int64 + example: [1541815603606036480, 1541815603606036827, 1541815287306036429] + description: Specifies members for specified organistion. + tags: + - Organisation + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully updated members. + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + '403': + description: User is not an organisation admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /organisation/{id}/campaign: + post: + operationId: createCampaign + parameters: + - name: id + in: path + description: Organisation ID + required: true + schema: + type: integer + format: int64 + description: Creates a new campaign inside specified organisation. + tags: + - Organisation + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: 2024 Subcommittee Recruitment + description: + type: string + example: Are you excited to make a difference? + starts_at: + type: string + example: 2024-03-15T18:25:43.511Z + ends_at: + type: string + example: 2024-04-15T18:25:43.511Z + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully created campaign. + '403': + description: User is not an admin of specified organisation. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /campaign: + get: + operationId: getAllCampaigns + description: Returns all active campaigns. + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + campaigns: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + example: 6996987893965262849 + organisation_id: + type: integer + format: int64 + example: 1541815603606036827 + organisation_name: + type: string + example: UNSW Software Development Society + name: + type: string + example: 2024 Subcommittee Recruitment + cover_image: + type: string + example: 2d19617b-46fd-4927-9f53-77d69232ba5d + description: + type: string + example: Are you excited to make a difference? + starts_at: + type: string + example: 2024-03-15T18:25:43.511Z + ends_at: + type: string + example: 2024-04-15T18:25:43.511Z + /campaign/{id}: + get: + operationId: getCampaignById + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + description: Returns info about specified campaign. + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + id: + type: integer + format: int64 + example: 6996987893965262849 + organisation_id: + type: integer + format: int64 + example: 1541815603606036827 + organisation_name: + type: string + example: UNSW Software Development Society + name: + type: string + example: 2024 Subcommittee Recruitment + cover_image: + type: string + example: 2d19617b-46fd-4927-9f53-77d69232ba5d + description: + type: string + example: Are you excited to make a difference? + starts_at: + type: string + example: 2024-03-15T18:25:43.511Z + ends_at: + type: string + example: 2024-04-15T18:25:43.511Z + put: + operationId: updateCampaignById + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: 2024 Subcommittee Recruitment + description: + type: string + example: Are you excited to make a difference? + starts_at: + type: string + example: 2024-03-15T18:25:43.511Z + ends_at: + type: string + example: 2024-04-15T18:25:43.511Z + description: Updates details of specified campaign. + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully updated campaign. + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + '403': + description: User is not an organisation admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + delete: + operationId: deleteCampaignById + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + description: Deletes specified campaign. + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully deleted campaign. + '403': + description: User is not an admin of campaign's organisation. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + /campaign/{id}/banner: + patch: + operationId: updateCampaignBannerById + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + description: Updates banner image for specified campaign. + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + upload_url: + type: string + description: Presigned S3 url to upload file. + example: https://presignedurldemo.s3.eu-west-2.amazonaws.com/6996987893965262849/2d19617b-46fd-4927-9f53-77d69232ba5d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJJWZ7B6WCRGMKFGQ%2F20180210%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20180210T171315Z&X-Amz-Expires=1800&X-Amz-Signature=12b74b0788aa036bc7c3d03b3f20c61f1f91cc9ad8873e3314255dc479a25351&X-Amz-SignedHeaders=host + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + '403': + description: User is not an organisation admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + + /campaign/{id}/role: + post: + operationId: createRole + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + description: Creates a new role in a campaign. + tags: + - Campaign + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: Chief Mouser + description: + type: string + required: False + example: Larry the cat is dead, now we need someone else to handle the rat issues at 10th Downing st. + min_available: + type: int32 + example: 1 + max_available: + type: int32 + example: 3 + finalised: + type: boolean + description: Whether this role has been finalised (e.g. max avaliable number) + example: False + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully created organisation. + '403': + description: User is not a Campaign Admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + + /campaign/{id}/roles: + get: + operationId: getRolesByCampaignId + parameters: + - name: id + in: path + description: Campaign ID + required: true + schema: + type: integer + format: int64 + description: Returns info about all roles in a campaign + tags: + - Campaign + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + campaigns: + type: array + items: + type: object + properties: + name: + type: string + example: Chief Mouser + description: + type: string + example: Larry the cat gone missing! now we need someone else to handle the rat issues at 10th Downing st. + min_available: + type: int32 + example: 1 + max_available: + type: int32 + example: 3 + finalised: + type: boolean + description: Whether this role has been finalised (e.g. max avaliable number) + example: False + /role/{id}: + get: + operationId: getRoleById + parameters: + - name: id + in: path + description: Role ID + required: true + schema: + type: integer + format: int32 + description: Returns info about specified role. + tags: + - Role + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + name: + type: string + example: Chief Mouser + description: + type: string + example: Larry the cat gone missing! now we need someone else to handle the rat issues at 10th Downing st. + min_available: + type: int32 + example: 1 + max_available: + type: int32 + example: 3 + finalised: + type: boolean + description: Whether this role has been finalised (e.g. max avaliable number) + example: False + '401': + description: Not logged in. + content: + application/json: + schema: + properties: + error: + type: string + example: Not logged in. + + put: + operationId: updateRoleById + parameters: + - name: id + in: path + description: Role ID + required: true + schema: + type: integer + format: int32 + description: Update a role given the role id. + tags: + - Role + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + example: Chief Whip + description: + type: string + required: False + example: Put a bit of stick about! + min_available: + type: int32 + example: 1 + max_available: + type: int32 + example: 3 + finalised: + type: boolean + description: Whether this role has been finalised (e.g. max avaliable number) + example: true + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully update organisation. + '403': + description: User is not a Campaign Admin. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized + + delete: + operationId: deleteRoleById + parameters: + - name: id + in: path + description: Role ID + required: true + schema: + type: integer + format: int32 + description: Deletes specified role. + tags: + - Role + responses: + '200': + description: OK + content: + application/json: + schema: + properties: + message: + type: string + example: Successfully deleted role. + '403': + description: User is not an admin of role's Campaign. + content: + application/json: + schema: + properties: + error: + type: string + example: Unauthorized \ No newline at end of file diff --git a/backend/migrations/00000000000000_diesel_initial_setup/down.sql b/backend/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f52609..00000000 --- a/backend/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/backend/migrations/00000000000000_diesel_initial_setup/up.sql b/backend/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1..00000000 --- a/backend/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/backend/migrations/2021-12-07-112918_create_users/down.sql b/backend/migrations/2021-12-07-112918_create_users/down.sql deleted file mode 100644 index cc1f647d..00000000 --- a/backend/migrations/2021-12-07-112918_create_users/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE users; diff --git a/backend/migrations/2021-12-07-112918_create_users/up.sql b/backend/migrations/2021-12-07-112918_create_users/up.sql deleted file mode 100644 index ebd7ab8f..00000000 --- a/backend/migrations/2021-12-07-112918_create_users/up.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE users ( - id SERIAL PRIMARY KEY, - email TEXT NOT NULL, - zid TEXT NOT NULL, - display_name TEXT NOT NULL, - degree_name TEXT NOT NULL, - degree_starting_year INTEGER NOT NULL, - superuser BOOLEAN NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('users'); diff --git a/backend/migrations/2021-12-07-114233_create_organisations/down.sql b/backend/migrations/2021-12-07-114233_create_organisations/down.sql deleted file mode 100644 index c0f04cf4..00000000 --- a/backend/migrations/2021-12-07-114233_create_organisations/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE organisations; diff --git a/backend/migrations/2021-12-07-114233_create_organisations/up.sql b/backend/migrations/2021-12-07-114233_create_organisations/up.sql deleted file mode 100644 index a608b61f..00000000 --- a/backend/migrations/2021-12-07-114233_create_organisations/up.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE organisations ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - logo BYTEA, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('organisations'); diff --git a/backend/migrations/2021-12-07-114404_create_organisation_users/down.sql b/backend/migrations/2021-12-07-114404_create_organisation_users/down.sql deleted file mode 100644 index 3677372d..00000000 --- a/backend/migrations/2021-12-07-114404_create_organisation_users/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE organisation_users; -DROP TYPE admin_level; diff --git a/backend/migrations/2021-12-07-114404_create_organisation_users/up.sql b/backend/migrations/2021-12-07-114404_create_organisation_users/up.sql deleted file mode 100644 index 25f191ff..00000000 --- a/backend/migrations/2021-12-07-114404_create_organisation_users/up.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TYPE admin_level AS ENUM ('ReadOnly', 'Director', 'Admin'); - -CREATE TABLE organisation_users ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (id), - organisation_id INTEGER NOT NULL REFERENCES organisations (id), - admin_level admin_level NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('organisation_users'); diff --git a/backend/migrations/2021-12-07-114733_campaigns/down.sql b/backend/migrations/2021-12-07-114733_campaigns/down.sql deleted file mode 100644 index 4f386f05..00000000 --- a/backend/migrations/2021-12-07-114733_campaigns/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE campaigns; diff --git a/backend/migrations/2021-12-07-114733_campaigns/up.sql b/backend/migrations/2021-12-07-114733_campaigns/up.sql deleted file mode 100644 index 1cb6f315..00000000 --- a/backend/migrations/2021-12-07-114733_campaigns/up.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE campaigns ( - id SERIAL PRIMARY KEY, - organisation_id INTEGER NOT NULL REFERENCES organisations (id), - name TEXT NOT NULL, - cover_image BYTEA, - description TEXT NOT NULL, - starts_at TIMESTAMP NOT NULL, - ends_at TIMESTAMP NOT NULL, - published BOOLEAN NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('campaigns'); diff --git a/backend/migrations/2021-12-07-114906_roles/down.sql b/backend/migrations/2021-12-07-114906_roles/down.sql deleted file mode 100644 index 0f9306dc..00000000 --- a/backend/migrations/2021-12-07-114906_roles/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE roles; diff --git a/backend/migrations/2021-12-07-114906_roles/up.sql b/backend/migrations/2021-12-07-114906_roles/up.sql deleted file mode 100644 index 4959761c..00000000 --- a/backend/migrations/2021-12-07-114906_roles/up.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE roles ( - id SERIAL PRIMARY KEY, - campaign_id INTEGER NOT NULL REFERENCES campaigns (id), - name TEXT NOT NULL, - description TEXT, - min_available INTEGER NOT NULL, - max_available INTEGER NOT NULL, - finalised BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('roles'); diff --git a/backend/migrations/2021-12-07-115409_questions/down.sql b/backend/migrations/2021-12-07-115409_questions/down.sql deleted file mode 100644 index 17135d60..00000000 --- a/backend/migrations/2021-12-07-115409_questions/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE questions; diff --git a/backend/migrations/2021-12-07-115409_questions/up.sql b/backend/migrations/2021-12-07-115409_questions/up.sql deleted file mode 100644 index 19cd4c39..00000000 --- a/backend/migrations/2021-12-07-115409_questions/up.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE questions ( - id SERIAL PRIMARY KEY, - role_id INTEGER NOT NULL REFERENCES roles (id), - title TEXT NOT NULL, - description TEXT, - max_bytes INTEGER NOT NULL, - required BOOLEAN NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('questions'); diff --git a/backend/migrations/2021-12-07-115608_applications/down.sql b/backend/migrations/2021-12-07-115608_applications/down.sql deleted file mode 100644 index 0739143e..00000000 --- a/backend/migrations/2021-12-07-115608_applications/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE applications; -DROP TYPE application_status; diff --git a/backend/migrations/2021-12-07-115608_applications/up.sql b/backend/migrations/2021-12-07-115608_applications/up.sql deleted file mode 100644 index 49d6292b..00000000 --- a/backend/migrations/2021-12-07-115608_applications/up.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TYPE application_status AS ENUM ('Pending', 'Rejected', 'Success'); - -CREATE TABLE applications ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (id), - role_id INTEGER NOT NULL REFERENCES roles (id), - status application_status NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('applications'); diff --git a/backend/migrations/2021-12-07-115832_answers/down.sql b/backend/migrations/2021-12-07-115832_answers/down.sql deleted file mode 100644 index 90ff272a..00000000 --- a/backend/migrations/2021-12-07-115832_answers/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE answers; diff --git a/backend/migrations/2021-12-07-115832_answers/up.sql b/backend/migrations/2021-12-07-115832_answers/up.sql deleted file mode 100644 index b3f50213..00000000 --- a/backend/migrations/2021-12-07-115832_answers/up.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE answers ( - id SERIAL PRIMARY KEY, - application_id INTEGER NOT NULL REFERENCES applications (id), - question_id INTEGER NOT NULL REFERENCES questions (id), - description TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('answers'); diff --git a/backend/migrations/2021-12-07-115931_comments/down.sql b/backend/migrations/2021-12-07-115931_comments/down.sql deleted file mode 100644 index 9ef7d126..00000000 --- a/backend/migrations/2021-12-07-115931_comments/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE comments; diff --git a/backend/migrations/2021-12-07-115931_comments/up.sql b/backend/migrations/2021-12-07-115931_comments/up.sql deleted file mode 100644 index c286ce29..00000000 --- a/backend/migrations/2021-12-07-115931_comments/up.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE comments ( - id SERIAL PRIMARY KEY, - application_id INTEGER NOT NULL REFERENCES applications (id), - commenter_user_id INTEGER NOT NULL REFERENCES users (id), - description TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('comments'); diff --git a/backend/migrations/2021-12-07-120033_ratings/down.sql b/backend/migrations/2021-12-07-120033_ratings/down.sql deleted file mode 100644 index c1ae6185..00000000 --- a/backend/migrations/2021-12-07-120033_ratings/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE ratings; diff --git a/backend/migrations/2021-12-07-120033_ratings/up.sql b/backend/migrations/2021-12-07-120033_ratings/up.sql deleted file mode 100644 index 432da2ff..00000000 --- a/backend/migrations/2021-12-07-120033_ratings/up.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE ratings ( - id SERIAL PRIMARY KEY, - application_id INTEGER NOT NULL REFERENCES applications (id), - rater_user_id INTEGER NOT NULL REFERENCES users (id), - rating INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -SELECT diesel_manage_updated_at('ratings'); diff --git a/backend/migrations/2022-05-30-145617_questions/down.sql b/backend/migrations/2022-05-30-145617_questions/down.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/migrations/2022-05-30-145617_questions/up.sql b/backend/migrations/2022-05-30-145617_questions/up.sql deleted file mode 100644 index c835b42d..00000000 --- a/backend/migrations/2022-05-30-145617_questions/up.sql +++ /dev/null @@ -1,20 +0,0 @@ -ALTER TABLE IF EXISTS questions - DROP CONSTRAINT questions_role_id_fkey; - -ALTER TABLE IF EXISTS questions - ALTER COLUMN role_id TYPE INTEGER[] - USING array[role_id]::INTEGER[]; - -ALTER TABLE IF EXISTS questions - RENAME COLUMN role_id TO role_ids; - -CREATE TABLE IF NOT EXISTS questions ( - id SERIAL PRIMARY KEY, - role_ids INTEGER[] NOT NULL, - title TEXT NOT NULL, - description TEXT, - max_bytes INTEGER NOT NULL, - required BOOLEAN NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/backend/migrations/2022-11-21-043159_application_private_statuses/down.sql b/backend/migrations/2022-11-21-043159_application_private_statuses/down.sql deleted file mode 100644 index 912ef129..00000000 --- a/backend/migrations/2022-11-21-043159_application_private_statuses/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE applications -DROP COLUMN private_status; diff --git a/backend/migrations/2022-11-21-043159_application_private_statuses/up.sql b/backend/migrations/2022-11-21-043159_application_private_statuses/up.sql deleted file mode 100644 index 9b9efd67..00000000 --- a/backend/migrations/2022-11-21-043159_application_private_statuses/up.sql +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE applications -ADD COLUMN private_status application_status; - -UPDATE applications -SET private_status = status; - -ALTER TABLE applications -ALTER COLUMN status -SET NOT NULL; diff --git a/backend/migrations/2022-12-04-065103_image_files/down.sql b/backend/migrations/2022-12-04-065103_image_files/down.sql deleted file mode 100644 index 1ba72d00..00000000 --- a/backend/migrations/2022-12-04-065103_image_files/down.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE organisations -ALTER COLUMN logo TYPE BYTEA USING NULL; - -ALTER TABLE campaigns -ALTER COLUMN cover_image TYPE BYTEA USING NULL; diff --git a/backend/migrations/2022-12-04-065103_image_files/up.sql b/backend/migrations/2022-12-04-065103_image_files/up.sql deleted file mode 100644 index ecafde66..00000000 --- a/backend/migrations/2022-12-04-065103_image_files/up.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE organisations -ALTER COLUMN logo TYPE TEXT USING NULL; - -ALTER TABLE campaigns -ALTER COLUMN cover_image TYPE TEXT USING NULL; diff --git a/backend/migrations/2023-06-07-042751_user_gender/down.sql b/backend/migrations/2023-06-07-042751_user_gender/down.sql deleted file mode 100644 index 0c5e3ef5..00000000 --- a/backend/migrations/2023-06-07-042751_user_gender/down.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE users -DROP COLUMN gender; - -DROP TYPE user_gender; diff --git a/backend/migrations/2023-06-07-042751_user_gender/up.sql b/backend/migrations/2023-06-07-042751_user_gender/up.sql deleted file mode 100644 index d6007cd3..00000000 --- a/backend/migrations/2023-06-07-042751_user_gender/up.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TYPE user_gender AS ENUM ('Female', 'Male', 'Unspecified'); - -ALTER TABLE users - ADD COLUMN gender user_gender DEFAULT 'Unspecified' NOT NULL; diff --git a/backend/migrations/2023-09-20-052638_user_pronouns/down.sql b/backend/migrations/2023-09-20-052638_user_pronouns/down.sql deleted file mode 100644 index 6cc4cb64..00000000 --- a/backend/migrations/2023-09-20-052638_user_pronouns/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users -DROP COLUMN pronouns; diff --git a/backend/migrations/2023-09-20-052638_user_pronouns/up.sql b/backend/migrations/2023-09-20-052638_user_pronouns/up.sql deleted file mode 100644 index ef54ae9c..00000000 --- a/backend/migrations/2023-09-20-052638_user_pronouns/up.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users - ADD COLUMN pronouns TEXT DEFAULT '' NOT NULL; diff --git a/backend/migrations/20240406023149_create_users.sql b/backend/migrations/20240406023149_create_users.sql new file mode 100644 index 00000000..8dc86bf3 --- /dev/null +++ b/backend/migrations/20240406023149_create_users.sql @@ -0,0 +1,17 @@ +CREATE TYPE user_role AS ENUM ('User', 'SuperUser'); + +CREATE TABLE users ( + id BIGINT PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + zid TEXT, + name TEXT NOT NULL, + pronouns TEXT NOT NULL, + gender TEXT NOT NULL, + degree_name TEXT, + degree_starting_year INTEGER, + role user_role NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX IDX_users_email_lower on users ((lower(email))); diff --git a/backend/migrations/20240406024211_create_organisations.sql b/backend/migrations/20240406024211_create_organisations.sql new file mode 100644 index 00000000..24f2c18d --- /dev/null +++ b/backend/migrations/20240406024211_create_organisations.sql @@ -0,0 +1,23 @@ +CREATE TABLE organisations ( + id BIGINT PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + logo UUID, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE TYPE organisation_role AS ENUM ('User', 'Admin'); + +CREATE TABLE organisation_members ( + id BIGSERIAL PRIMARY KEY, + organisation_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + role organisation_role DEFAULT 'User' NOT NULL, + CONSTRAINT FK_organisation_members_organisation + FOREIGN KEY(organisation_id) + REFERENCES organisations(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_organisation_admins_organisation on organisation_members (organisation_id); diff --git a/backend/migrations/20240406025537_create_campaigns.sql b/backend/migrations/20240406025537_create_campaigns.sql new file mode 100644 index 00000000..ebc1311c --- /dev/null +++ b/backend/migrations/20240406025537_create_campaigns.sql @@ -0,0 +1,35 @@ +CREATE TABLE campaigns ( + id BIGINT PRIMARY KEY, + organisation_id BIGINT NOT NULL, + name TEXT NOT NULL, + cover_image UUID, + description TEXT, + starts_at TIMESTAMPTZ NOT NULL, + ends_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_campaigns_organisations + FOREIGN KEY(organisation_id) + REFERENCES organisations(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE campaign_roles ( + id BIGINT PRIMARY KEY, + campaign_id BIGINT NOT NULL, + name TEXT NOT NULL, + description TEXT, + min_available INTEGER NOT NULL, + max_available INTEGER NOT NULL, + finalised BOOLEAN NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_campaign_roles_campaign + FOREIGN KEY(campaign_id) + REFERENCES campaigns(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_campaign_roles_campaign on campaign_roles (campaign_id); diff --git a/backend/migrations/20240406031400_create_questions.sql b/backend/migrations/20240406031400_create_questions.sql new file mode 100644 index 00000000..8e7136af --- /dev/null +++ b/backend/migrations/20240406031400_create_questions.sql @@ -0,0 +1,30 @@ +CREATE TYPE question_type AS ENUM ('ShortAnswer', 'MultiChoice', 'MultiSelect', 'DropDown'); + +CREATE TABLE questions ( + id BIGINT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + required BOOLEAN, + question_type question_type NOT NULL, + campaign_id BIGINT NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_questions_campaigns + FOREIGN KEY(campaign_id) + REFERENCES campaigns(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE multi_option_question_options ( + id BIGSERIAL PRIMARY KEY, + text TEXT NOT NULL, + question_id INTEGER NOT NULL, + CONSTRAINT FK_multi_option_question_options_questions + FOREIGN KEY(question_id) + REFERENCES questions(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_multi_option_question_options_questions on multi_option_question_options (question_id); diff --git a/backend/migrations/20240406031915_create_applications.sql b/backend/migrations/20240406031915_create_applications.sql new file mode 100644 index 00000000..e2fcd057 --- /dev/null +++ b/backend/migrations/20240406031915_create_applications.sql @@ -0,0 +1,113 @@ +CREATE TYPE application_status AS ENUM ('Pending', 'Rejected', 'Successful'); + +CREATE TABLE applications ( + id BIGINT PRIMARY KEY, + campaign_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + status application_status NOT NULL DEFAULT 'Pending', + private_status application_status NOT NULL DEFAULT 'Pending', + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_applications_campaigns + FOREIGN KEY(campaign_id) + REFERENCES campaigns(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT FK_applications_users + FOREIGN KEY(user_id) + REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE application_roles ( + id BIGSERIAL PRIMARY KEY, + application_id BIGINT NOT NULL, + campaign_role_id BIGINT NOT NULL, + CONSTRAINT FK_application_roles_applications + FOREIGN KEY(application_id) + REFERENCES applications(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT FK_application_roles_campaign_roles + FOREIGN KEY(campaign_role_id) + REFERENCES campaign_roles(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_application_roles_applications on application_roles (application_id); +CREATE INDEX IDX_application_roles_campaign_roles on application_roles (campaign_role_id); + +CREATE TABLE answers ( + id BIGSERIAL PRIMARY KEY, + application_id BIGINT NOT NULL, + question_id BIGINT NOT NULL, + CONSTRAINT FK_answers_applications + FOREIGN KEY(application_id) + REFERENCES applications(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT FK_answers_questions + FOREIGN KEY(question_id) + REFERENCES questions(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_answers_applications on answers (application_id); +CREATE INDEX IDX_answers_questions on answers (question_id); + +CREATE TABLE short_answer_answers ( + id BIGSERIAL PRIMARY KEY, + text TEXT NOT NULL, + answer_id INTEGER NOT NULL, + CONSTRAINT FK_short_answer_answers_answers + FOREIGN KEY(answer_id) + REFERENCES answers(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_short_answer_answers_answers on short_answer_answers (answer_id); + +CREATE TABLE multi_option_answer_options ( + id BIGSERIAL PRIMARY KEY, + option_id BIGINT NOT NULL, + answer_id INTEGER NOT NULL, + CONSTRAINT FK_multi_option_answer_options_question_options + FOREIGN KEY(option_id) + REFERENCES multi_option_question_options(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT FK_multi_option_answer_options_answers + FOREIGN KEY(answer_id) + REFERENCES answers(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_multi_option_answer_options_question_options on multi_option_answer_options (option_id); +CREATE INDEX IDX_multi_option_answer_options_answers on multi_option_answer_options (answer_id); + +CREATE TABLE application_ratings ( + id BIGSERIAL PRIMARY KEY, + application_id BIGINT NOT NULL, + rater_id BIGINT NOT NULL, + rating INTEGER NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_application_ratings_applications + FOREIGN KEY(application_id) + REFERENCES applications(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT FK_application_ratings_users + FOREIGN KEY(rater_id) + REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX IDX_application_ratings_applications on application_ratings (application_id); +CREATE INDEX IDX_application_ratings_users on application_ratings (rater_id); diff --git a/backend/scripts/become_super_user.sh b/backend/scripts/become_super_user.sh deleted file mode 100755 index 1f15a822..00000000 --- a/backend/scripts/become_super_user.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -echo "Starting the script" -echo "====================" -echo "what is the email for the account?" -read SUPERUSER_EMAIL - - -echo "make $SUPERUSER_EMAIL a superuser? (y/n)" -read SUPERUSER_ANSWER -if [ "$SUPERUSER_ANSWER" == "n" ]; then - echo "skipping superuser creation" - exit -fi - - - -# expose env variables from .env -if [ -f .env ] -then - export $(cat .env | sed 's/#.*//g' | xargs) -else - echo "no .env file found" - exit -fi - -echo "db url is $DATABASE_URL" - -psql $DATABASE_URL << EOF - - UPDATE users SET superuser = true WHERE email = '$SUPERUSER_EMAIL'; - -EOF diff --git a/backend/scripts/seed.sh b/backend/scripts/seed.sh deleted file mode 100755 index 4054cd63..00000000 --- a/backend/scripts/seed.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -echo "WARNING - this will WIPE your local database" -echo "WARNING - this will WIPE your local changes to database/schema.rs" -echo "Are you sure you want to continue? (y/n)" -read answer -if [ "$answer" != "y" ]; then - echo "Aborting" - exit 1 -fi - -echo "Deleting images directory" -rm -rf images - -cd server/src && diesel database reset - -cd ../../seed_data && cargo run --bin seed_data - -mv images .. diff --git a/backend/seed_data/Cargo.toml b/backend/seed_data/Cargo.toml deleted file mode 100644 index 1daa0055..00000000 --- a/backend/seed_data/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "seed_data" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -server = { path="../server" } -diesel = "1.4.8" -chrono = "0.4.23" -dotenv = "0.15.0" diff --git a/backend/seed_data/assets/180DC.png b/backend/seed_data/assets/180DC.png deleted file mode 100644 index beccfcfc..00000000 Binary files a/backend/seed_data/assets/180DC.png and /dev/null differ diff --git a/backend/seed_data/assets/csesoc_logo.png b/backend/seed_data/assets/csesoc_logo.png deleted file mode 100644 index 847d441d..00000000 Binary files a/backend/seed_data/assets/csesoc_logo.png and /dev/null differ diff --git a/backend/seed_data/assets/csesoc_peer_mentoring.jpg b/backend/seed_data/assets/csesoc_peer_mentoring.jpg deleted file mode 100644 index e2da054c..00000000 Binary files a/backend/seed_data/assets/csesoc_peer_mentoring.jpg and /dev/null differ diff --git a/backend/seed_data/src/main.rs b/backend/seed_data/src/main.rs deleted file mode 100644 index feb58340..00000000 --- a/backend/seed_data/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod seed; - -use dotenv; - -fn main() { - dotenv::dotenv().ok(); - seed::seed(); -} diff --git a/backend/seed_data/src/seed.rs b/backend/seed_data/src/seed.rs deleted file mode 100644 index f542faa2..00000000 --- a/backend/seed_data/src/seed.rs +++ /dev/null @@ -1,311 +0,0 @@ -#![allow(unused_variables)] - -use backend::database::models::*; -use backend::database::schema::{AdminLevel, ApplicationStatus, UserGender}; -use backend::images::{save_image, try_decode_bytes}; -use chrono::naive::NaiveDate; -use diesel::pg::PgConnection; -use diesel::prelude::*; -use std::env; - -pub fn establish_connection() -> PgConnection { - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url)) -} - -pub fn seed() { - println!("SEEDING\n"); - - let connection = establish_connection(); - let users = vec![ - NewUser { - email: "shrey.somaiya@gmail.com".to_string(), - zid: "z5257343".to_string(), - display_name: "Shrey Somaiya".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2019, - gender: UserGender::Unspecified, - pronouns: "they/them".to_string(), - superuser: true, - }, - NewUser { - email: "fake.user@gmail.com".to_string(), - zid: "z1234567".to_string(), - display_name: "Fake User".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2019, - gender: UserGender::Unspecified, - pronouns: "".to_string(), - superuser: false, - }, - NewUser { - email: "michael.gribben@gmail.com".to_string(), - zid: "z5259232".to_string(), - display_name: "Michael Gribben".to_string(), - degree_name: "B. Eng (Software)".to_string(), - degree_starting_year: 2019, - gender: UserGender::Male, - pronouns: "he/him".to_string(), - superuser: false, - }, - NewUser { - email: "giuliana.debellis@gmail.com".to_string(), - zid: "z5259232".to_string(), - display_name: "Giuliana Debellis".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2020, - gender: UserGender::Female, - pronouns: "she/her".to_string(), - superuser: false, - }, - NewUser { - email: "lachlan.ting@gmail.com".to_string(), - zid: "z5264855".to_string(), - display_name: "Lachlan Ting".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2019, - gender: UserGender::Male, - pronouns: "he/him".to_string(), - superuser: false, - }, - NewUser { - email: "hayes.choy@gmail.com".to_string(), - zid: "z528816".to_string(), - display_name: "Hayes Choi".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2020, - gender: UserGender::Male, - pronouns: "he/him".to_string(), - superuser: false, - }, - NewUser { - email: "clarence.feng@gmail.com".to_string(), - zid: "z5260633".to_string(), - display_name: "Clarence Feng".to_string(), - degree_name: "B. CompSci".to_string(), - degree_starting_year: 2020, - gender: UserGender::Male, - pronouns: "he/him".to_string(), - superuser: false, - }, - ]; - - // add all users - for user in &users { - user.insert(&connection) - .expect(&format!("Failed to insert user {}.", user.display_name)); - } - println!("... Added {} users\n", users.len()); - - // create two organisations - let csesoc_org_logo_id = "d6b7b23d-064b-40f2-9b73-9a4cd32ee9c6"; - let degrees_org_logo_id = "adebf7f3-aa1e-4712-b5ca-051430bfaf8e"; - let csesoc_org_logo = try_decode_bytes(std::fs::read("./assets/csesoc_logo.png").unwrap()) - .expect("./assets/csesoc_logo.png missing!"); - let degrees_org_logo = try_decode_bytes(std::fs::read("./assets/180DC.png").unwrap()) - .expect("./assets/180DC.png missing!"); - save_image( - csesoc_org_logo, - backend::images::ImageLocation::ORGANISATIONS, - csesoc_org_logo_id, - ) - .expect("Failed saving CSESoc Logo"); - save_image( - degrees_org_logo, - backend::images::ImageLocation::ORGANISATIONS, - degrees_org_logo_id, - ) - .expect("Failed saving 180DC Logo"); - - let orgs = vec![ - NewOrganisation { - name: "CSESoc UNSW".to_string(), - logo: Some(csesoc_org_logo_id.to_string()), - }, - NewOrganisation { - name: "180 Degrees Consulting".to_string(), - logo: Some(degrees_org_logo_id.to_string()), - }, - ]; - - for org in &orgs { - org.insert(&connection) - .expect(&format!("Failed to insert org {}.", org.name)); - } - - assert!(Organisation::get_all(&connection).len() == 2); - - println!("... Added {} organizations\n", orgs.len()); - // make giuliana the admin of csesoc - - let giuliana_user = User::get_from_email(&connection, "giuliana.debellis@gmail.com") - .expect("Failed to get giuliana user from email"); - - let csesoc_org = - Organisation::find_by_name(&connection, "CSESoc UNSW").expect("csesoc should exist"); - - let giuliana_csesoc_admin = NewOrganisationUser { - user_id: giuliana_user.id, - organisation_id: csesoc_org.id, - admin_level: AdminLevel::Admin, - } - .insert(&connection) - .expect("Failed to insert giuliana as admin"); - - println!("... Adding guiuliana as csesoc admin\n"); - - // make clarence a director of csesoc - let clarence_user = User::get_from_email(&connection, "clarence.feng@gmail.com") - .expect("Failed to get giuliana user from email"); - - let clarence_csesoc_director = NewOrganisationUser { - user_id: clarence_user.id, - organisation_id: csesoc_org.id, - admin_level: AdminLevel::Director, - } - .insert(&connection) - .expect("failed to insert org user clarence"); - - println!("... Adding clarence as csesoc director\n"); - // create peer mentoring campaign for csesoc - - let peer_mentoring_logo_id = "523fde49-027a-4fc8-b296-aaefe9e215d6"; - let peer_mentoring_logo = - try_decode_bytes(std::fs::read("./assets/csesoc_peer_mentoring.jpg").unwrap()) - .expect("./assets/csesoc_peer_mentoring.jpg missing!"); - save_image( - peer_mentoring_logo, - backend::images::ImageLocation::CAMPAIGNS, - peer_mentoring_logo_id, - ) - .expect("Failed saving Peer Mentoring Logo"); - - let new_campaign = NewCampaign { - name: "2022 Peer Mentor Recruitment".to_string(), - description: "Peer mentors are an important part of CSESoc and university life at UNSW. We are looking for enthusiastic students who are passionate about helping first-year students, gaining leadership experience, communication skills, some resume-worthy additions, and having a lot of fun in the upcoming term (Term 1, 2022)! 🎉".to_string(), - organisation_id: csesoc_org.id, - starts_at: NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(10, 00, 00).unwrap(), - ends_at: NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().and_hms_opt(23, 59, 59).unwrap(), - cover_image: Some(peer_mentoring_logo_id.to_string()), - published: true, - }.insert(&connection).expect("failed to insert new campaign"); - - println!("... Creating peer mentoring campaign\n"); - - let mentor_role = RoleUpdate { - campaign_id: new_campaign.id, - name: "Peer Mentor".to_string(), - description: Some("help students 5head".to_string()), - min_available: 70, - max_available: 100, - finalised: false, - } - .insert(&connection) - .expect("Failed to insert Peer Mentor role"); - - let senior_mentor_role = RoleUpdate { - campaign_id: new_campaign.id, - name: "Senior Mentor".to_string(), - description: Some("help with organisation".to_string()), - min_available: 1, - max_available: 3, - finalised: false, - } - .insert(&connection) - .expect("Failed to insert senior mentor role"); - - println!("... Creating peer mentor and senior mentor role\n"); - // attatch two questions two senior mentor role - let question_one = NewQuestion { - title: "What is the meaning of life?".to_string(), - max_bytes: 100, - role_ids: vec![senior_mentor_role.id], - required: false, - description: Some("Please ensure to go into great detail!".to_string()), - } - .insert(&connection) - .expect("Failed to insert question"); - - let question_two = NewQuestion { - title: "Why do you want to be a Peer Mentor".to_string(), - max_bytes: 300, - role_ids: vec![senior_mentor_role.id, mentor_role.id], - required: true, - description: Some("Please explain why you would like to be a peer mentor!".to_string()), - } - .insert(&connection) - .expect("Failed to insert question"); - - println!("... Creating senior mentor questions\n"); - // hayes choy wants to apply for the senior peer mentor role - - let application = NewApplication { - role_id: senior_mentor_role.id, - user_id: User::get_from_email(&connection, "hayes.choy@gmail.com") - .unwrap() - .id, - status: ApplicationStatus::Pending, - private_status: ApplicationStatus::Pending, - } - .insert(&connection) - .expect("Failed to insert application"); - - let application = NewApplication { - role_id: senior_mentor_role.id, - user_id: User::get_from_email(&connection, "shrey.somaiya@gmail.com") - .unwrap() - .id, - status: ApplicationStatus::Pending, - private_status: ApplicationStatus::Pending, - } - .insert(&connection) - .expect("Failed to insert application"); - - println!("... Creating hayes application\n"); - - // create answers to question one - let hayes_qn_one_answer = NewAnswer { - question_id: question_one.id, - application_id: application.id, - description: "42".to_string(), - } - .insert(&connection) - .expect("Failed to insert answer"); - - println!("... Creating hayes answer to question one\n"); - // lets create a rating for hayes from Giuliana - - let hayes_rating_from_giuliana = NewRating { - application_id: application.id, - rater_user_id: giuliana_csesoc_admin.user_id, - rating: 0, - } - .insert(&connection) - .expect("Failed to insert rating"); - - let hayes_rating_from_clarence = NewRating { - application_id: application.id, - rater_user_id: clarence_csesoc_director.user_id, - rating: 5, - } - .insert(&connection) - .expect("Failed to insert rating"); - - let hayes_comment_from_giuliana = NewComment { - application_id: application.id, - commenter_user_id: giuliana_csesoc_admin.user_id, - description: "bad answers".to_string(), - } - .insert(&connection) - .expect("Failed to insert comment"); - - let hayes_comment_from_clarence = NewComment { - application_id: application.id, - commenter_user_id: clarence_csesoc_director.user_id, - description: "love this guy <3".to_string(), - } - .insert(&connection) - .expect("Failed to insert comment"); - - println!("... Creating hayes comments and ratings\n"); -} diff --git a/backend/server/Cargo.toml b/backend/server/Cargo.toml index a0e60570..9c4babe2 100644 --- a/backend/server/Cargo.toml +++ b/backend/server/Cargo.toml @@ -3,38 +3,27 @@ name = "server" version = "0.1.0" edition = "2021" - -[lib] -name = "backend" -path = "src/lib.rs" - -[[bin]] -name = "server" -path = "src/bin.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rocket = { version = "0.5.0-rc.2", features = ["json"] } -rocket_sync_db_pools = { version = "0.1.0-rc.2", features = ["diesel_postgres_pool"] } -# pull rocket_cors from git master until crates.io artifact builds on stable -rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" } -diesel = { version = "1.4.8", features = ["postgres", "r2d2", "chrono"] } -diesel-derive-enum = { version = "1", features = ["postgres"] } -dotenv = "0.15.0" -dotenv_codegen = "0.15.0" -reqwest = { version = "0.11.13", features = ["json"] } -jsonwebtoken = "8.3.0" -serde = {version = "1.0", features = ["derive"] } -serde_json = "1.0.89" +# Primary crates +tokio = { version = "1.34", features = ["macros", "rt-multi-thread"] } +axum = { version = "0.7", features = ["macros"] } +axum-extra = { version = "0.9", features = ["typed-header"] } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] } + +# Important secondary crates +anyhow = "1.0" +thiserror = "1.0" +serde = { version = "1.0", features = ["derive"] } +reqwest = { version = "0.11", features = ["json"] } +serde_json = "1.0" chrono = { version = "0.4", features = ["serde"] } -itertools = "0.10.5" -once_cell = "1.18.0" -diesel_migrations = "1.4.0" -figment = { version = "0.10", features = ["env", "toml", "json"] } -image = "0.24.4" -strum = { version = "0.24", features = ["derive"] } -webp = "0.2" +oauth2 = "4.4" +log = "0.4" +uuid = { version = "1.5", features = ["serde", "v4"] } +rust-s3 = "0.34.0" +rs-snowflake = "0.6" +jsonwebtoken = "9.1" +dotenvy = "0.15" -[dependencies.uuid] -version = "1.3.3" -features = ["v4", "fast-rng", "macro-diagnostics"] diff --git a/backend/server/src/Cargo.lock b/backend/server/src/Cargo.lock deleted file mode 100644 index df30c94f..00000000 --- a/backend/server/src/Cargo.lock +++ /dev/null @@ -1,2078 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backend" -version = "0.1.0" -dependencies = [ - "chrono", - "diesel", - "diesel-derive-enum", - "dotenv", - "dotenv_codegen", - "jsonwebtoken", - "reqwest", - "rocket", - "rocket_cors", - "rocket_sync_db_pools", - "serde", - "serde_json", -] - -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "serde", - "time 0.1.43", - "winapi", -] - -[[package]] -name = "const_fn" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" - -[[package]] -name = "cookie" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" -dependencies = [ - "percent-encoding", - "time 0.2.27", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "devise" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" -dependencies = [ - "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] -name = "diesel" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" -dependencies = [ - "bitflags", - "byteorder", - "chrono", - "diesel_derives", - "pq-sys", - "r2d2", -] - -[[package]] -name = "diesel-derive-enum" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70806b70be328e646f243680a3fc93b3cfdd6db373faa5110660a5dd5af243bc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "diesel_derives" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dotenv_codegen" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56966279c10e4f8ee8c22123a15ed74e7c8150b658b26c619c53f4a56eb4a8aa" -dependencies = [ - "dotenv_codegen_implementation", - "proc-macro-hack", -] - -[[package]] -name = "dotenv_codegen_implementation" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e737a3522cd45f6adc19b644ce43ef53e1e9045f2d2de425c1f468abd4cf33" -dependencies = [ - "dotenv", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encoding_rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "figment" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" -dependencies = [ - "atomic", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" - -[[package]] -name = "futures-executor" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" - -[[package]] -name = "futures-macro" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" - -[[package]] -name = "futures-task" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" - -[[package]] -name = "futures-util" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi", -] - -[[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "h2" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "http" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" - -[[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.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "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 = "indexmap" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" -dependencies = [ - "autocfg", - "hashbrown", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" -dependencies = [ - "base64 0.12.3", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" - -[[package]] -name = "lock_api" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "loom" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "multer" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "mime", - "spin 0.9.2", - "tokio", - "tokio-util", - "twoway", - "version_check", -] - -[[package]] -name = "native-tls" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" - -[[package]] -name = "openssl-sys" -version = "0.9.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - -[[package]] -name = "pear" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.0", - "once_cell", - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project-lite" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" - -[[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.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" - -[[package]] -name = "ppv-lite86" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" - -[[package]] -name = "pq-sys" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" -dependencies = [ - "vcpkg", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", -] - -[[package]] -name = "quote" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r2d2" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" -dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reqwest" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bea77bc708afa10e59905c3d4af7c8fd43c9214251673095ff8b14345fcbc5" -dependencies = [ - "base64 0.13.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rocket" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" -dependencies = [ - "async-stream", - "async-trait", - "atomic", - "atty", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time 0.2.27", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn", - "unicode-xid", -] - -[[package]] -name = "rocket_cors" -version = "0.5.2" -source = "git+https://github.com/lawliet89/rocket_cors?branch=master#2ec5b3e0918c5ed634baeec3d1948f096f3c534d" -dependencies = [ - "log", - "regex", - "rocket", - "serde", - "serde_derive", - "unicase", - "unicase_serde", - "url", -] - -[[package]] -name = "rocket_http" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" -dependencies = [ - "cookie", - "either", - "http", - "hyper", - "indexmap", - "log", - "memchr", - "mime", - "parking_lot", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time 0.2.27", - "tokio", - "uncased", -] - -[[package]] -name = "rocket_sync_db_pools" -version = "0.1.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cfdfebd552d075c368e641c88a5cd6ce1c58c5c710548aeb777abb48830f4b" -dependencies = [ - "diesel", - "r2d2", - "rocket", - "rocket_sync_db_pools_codegen", - "serde", - "tokio", -] - -[[package]] -name = "rocket_sync_db_pools_codegen" -version = "0.1.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267808c094db5366e1d8925aaf9f2ce05ff9b3bd92cb18c7040a1fe219c2e25" -dependencies = [ - "devise", - "quote", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "ryu" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - -[[package]] -name = "scheduled-thread-pool" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "security-framework" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "simple_asn1" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" -dependencies = [ - "chrono", - "num-bigint", - "num-traits", -] - -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - -[[package]] -name = "smallvec" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" - -[[package]] -name = "socket2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "state" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" -dependencies = [ - "loom", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - -[[package]] -name = "syn" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" -dependencies = [ - "once_cell", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "signal-hook-registry", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-log" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3" -dependencies = [ - "ansi_term", - "lazy_static", - "matchers", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "twoway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" -dependencies = [ - "memchr", - "unchecked-index", -] - -[[package]] -name = "ubyte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" -dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" -dependencies = [ - "serde", - "version_check", -] - -[[package]] -name = "unchecked-index" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicase_serde" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef53697679d874d69f3160af80bc28de12730a985d57bdf2b47456ccb8b11f1" -dependencies = [ - "serde", - "unicase", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[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.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasm-bindgen" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "web-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[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 = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] - -[[package]] -name = "yansi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" diff --git a/backend/server/src/admin.rs b/backend/server/src/admin.rs deleted file mode 100644 index ce942ba5..00000000 --- a/backend/server/src/admin.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::database::{ - models::{AdminInfoResponse, OrganisationInfo, OrganisationUser, SuperUser, User}, - Database, -}; -use crate::error::JsonErr; -use rocket::{get, http::Status, post, serde::json::Json}; - -#[get("/")] -pub async fn get(user: User, db: Database) -> Json { - Json(AdminInfoResponse { - organisations: db - .run(move |conn| { - user.get_all_org_ids_belonging(conn) - .into_iter() - .filter(|org_id| { - OrganisationUser::organisation_admin_level(*org_id, user.id, conn) - .is_at_least_director() - .check() - .is_ok() - }) - .map(|org| OrganisationInfo::new(org, conn)) - .collect::>() - }) - .await, - }) -} - -#[post("/make_superuser", data = "")] -pub async fn make_superuser( - _user: SuperUser, - db: Database, - email: Json, -) -> Result<(), JsonErr<()>> { - db.run(move |conn| { - User::get_from_email(conn, &email.into_inner()) - .map(|user| user.make_superuser(conn).ok()) - .flatten() - }) - .await - .ok_or(JsonErr((), Status::BadRequest)) -} diff --git a/backend/server/src/application.rs b/backend/server/src/application.rs deleted file mode 100644 index 90798496..00000000 --- a/backend/server/src/application.rs +++ /dev/null @@ -1,292 +0,0 @@ -use diesel::prelude::*; - -use crate::database::{ - models::{ - Answer, Application, Campaign, Comment, NewAnswer, NewApplication, NewRating, - OrganisationUser, Question, Rating, Role, User, - }, - schema::ApplicationStatus, - Database, -}; -use crate::error::JsonErr; -use rocket::{ - get, - http::Status, - post, put, - serde::{json::Json, Deserialize, Serialize}, - FromForm, -}; - -#[derive(Serialize)] -pub enum ApplicationError { - Unauthorized, - UserNotFound, - RoleNotFound, - UnableToCreate, - UnableToUpdate, - AppNotFound, - QuestionNotFound, - InvalidInput, - CampaignEnded, -} - -#[derive(Deserialize)] -pub struct ApplicationReq { - pub role_id: i32, -} - -#[post("/", data = "")] -pub async fn create_application( - app_req: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - use crate::database::schema::applications::dsl::*; - use diesel::prelude::*; - use diesel::query_dsl::RunQueryDsl; - - let new_application = NewApplication { - user_id: user.id, - role_id: app_req.role_id, - status: ApplicationStatus::Pending, - private_status: ApplicationStatus::Pending, - }; - - let application = db - .run(move |conn| { - let role = Role::get_from_id(conn, app_req.role_id)?; - let camp = Campaign::get_from_id(conn, role.campaign_id)?; - - if !camp.is_running() { - return None; - } - - let count = applications - .filter(role_id.eq(app_req.role_id).and(user_id.eq(user.id))) - .select(id) - .load::(conn) - .unwrap_or_else(|_| vec![]) - .len(); - - if count > 0 { - return None; - } - - NewApplication::insert(&new_application, conn) - }) - .await - .ok_or(JsonErr( - ApplicationError::UnableToCreate, - Status::BadRequest, - ))?; - - Ok(Json(application)) -} - -#[derive(FromForm, Deserialize)] -pub struct RatingInput { - rating: i32, -} - -#[put("//rating", data = "")] -pub async fn create_rating( - application_id: i32, - rating: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - OrganisationUser::application_admin_level(application_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - NewRating::insert( - &NewRating { - application_id, - rater_user_id: user.id, - rating: rating.rating, - }, - &conn, - ) - .ok_or(JsonErr( - ApplicationError::UnableToCreate, - Status::InternalServerError, - ))?; - - Ok(Json(())) - }) - .await -} - -#[post("/answer", data = "")] -pub async fn submit_answer( - user: User, - db: Database, - answer: Json, -) -> Result, JsonErr> { - db.run(move |conn| { - let application = Application::get(answer.application_id, &conn) - .ok_or(JsonErr(ApplicationError::AppNotFound, Status::NotFound))?; - if application.user_id != user.id { - return Err(JsonErr(ApplicationError::Unauthorized, Status::Forbidden)); - } - - let question = Question::get_from_id(&conn, answer.question_id).ok_or(JsonErr( - ApplicationError::QuestionNotFound, - Status::BadRequest, - ))?; - if answer.description.len() as i32 > question.max_bytes { - return Err(JsonErr(ApplicationError::InvalidInput, Status::BadRequest)); - } - - NewAnswer::insert(&answer, &conn).ok_or(JsonErr( - ApplicationError::UnableToCreate, - Status::InternalServerError, - ))?; - - Ok(Json(())) - }) - .await -} - -#[derive(Serialize)] -pub struct AnswersResponse { - answers: Vec, -} - -#[get("//answers")] -pub async fn get_answers( - application_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let app = Application::get(application_id, &conn) - .ok_or(JsonErr(ApplicationError::AppNotFound, Status::NotFound))?; - - OrganisationUser::role_admin_level(app.role_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - Ok(Json(AnswersResponse { - answers: Answer::get_all_from_application_id(conn, application_id), - })) - }) - .await -} - -#[derive(Serialize)] -pub struct RatingsResponse { - ratings: Vec, -} - -#[get("//ratings")] -pub async fn get_ratings( - application_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let app = Application::get(application_id, &conn) - .ok_or(JsonErr(ApplicationError::AppNotFound, Status::NotFound))?; - - OrganisationUser::role_admin_level(app.role_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - Ok(Json(RatingsResponse { - ratings: Rating::get_all_from_application_id(conn, application_id), - })) - }) - .await -} - -#[put("//status", data = "")] -pub async fn set_status( - application_id: i32, - new_status: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - use crate::database::schema::applications::dsl::*; - - db.run(move |conn| { - OrganisationUser::application_admin_level(application_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - diesel::update(applications.filter(id.eq(application_id))) - .set(status.eq(new_status.into_inner())) - .execute(conn) - .map_err(|_| { - JsonErr( - ApplicationError::UnableToUpdate, - Status::InternalServerError, - ) - })?; - - Ok(Json(())) - }) - .await -} - -#[put("//private_status", data = "")] -pub async fn set_private_status( - application_id: i32, - new_status: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - use crate::database::schema::applications::dsl::*; - - db.run(move |conn| { - OrganisationUser::application_admin_level(application_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - diesel::update(applications.filter(id.eq(application_id))) - .set(private_status.eq(new_status.into_inner())) - .execute(conn) - .map_err(|_| { - JsonErr( - ApplicationError::UnableToUpdate, - Status::InternalServerError, - ) - })?; - - Ok(Json(())) - }) - .await -} - -#[derive(Serialize)] -pub struct CommentsResponse { - comments: Vec, -} - -#[get("//comments")] -pub async fn get_comments( - application_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let app = Application::get(application_id, &conn) - .ok_or(JsonErr(ApplicationError::AppNotFound, Status::NotFound))?; - - OrganisationUser::role_admin_level(app.role_id, user.id, &conn) - .is_at_least_director() - .check() - .map_err(|_| JsonErr(ApplicationError::Unauthorized, Status::Forbidden))?; - - Ok(Json(CommentsResponse { - comments: Comment::get_all_from_application_id(conn, application_id), - })) - }) - .await -} diff --git a/backend/server/src/auth.rs b/backend/server/src/auth.rs deleted file mode 100644 index 4f7e805e..00000000 --- a/backend/server/src/auth.rs +++ /dev/null @@ -1,376 +0,0 @@ -use crate::error::JsonErr; -use crate::{ - database::{ - models::{NewUser, User}, - schema::UserGender, - Database, - }, - state::ApiState, -}; -use dotenv; -use jsonwebtoken::{Algorithm, Header, Validation}; -use reqwest::header; -use rocket::{ - http::Status, - post, - request::FromRequest, - request::{self, Outcome}, - serde::json::Json, - Request, State, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -const GOOGLE_TOKEN_URL: &str = "https://oauth2.googleapis.com/token"; -const OPENID_EMAIL_FIELD: &str = "email"; -const OPENID_NAME_FIELD: &str = "name"; - -#[derive(Debug, Serialize, Deserialize)] -pub struct AuthJwt { - pub user_id: u32, -} - -pub struct Auth { - pub jwt: AuthJwt, -} - -#[derive(Debug)] -pub enum AuthError { - MissingAuthorizationHeader, - MissingBearer, - InvalidJwt, - ApiStateMissing, - NotSuperUser, -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for Auth { - type Error = AuthError; - - async fn from_request(request: &'r Request<'_>) -> request::Outcome { - let api_state = match request.guard::<&State>().await { - Outcome::Success(api_state) => api_state, - _ => { - return Outcome::Failure((Status::InternalServerError, AuthError::ApiStateMissing)) - } - }; - - let header = match request.headers().get_one("Authorization") { - Some(header) => header, - None => { - return Outcome::Failure(( - Status::BadRequest, - AuthError::MissingAuthorizationHeader, - )) - } - }; - - if !header.starts_with("Bearer ") { - return Outcome::Failure((Status::BadRequest, AuthError::MissingBearer)); - } - - let token = header.trim_start_matches("Bearer "); - - let mut validation = Validation::new(Algorithm::HS256); - validation.validate_exp = false; - validation.required_spec_claims.clear(); - - let token = match jsonwebtoken::decode::( - token, - &api_state.jwt_decoding_key, - &validation, - ) { - Ok(data) => data.claims, - Err(_) => return Outcome::Failure((Status::BadRequest, AuthError::InvalidJwt)), - }; - - return Outcome::Success(Auth { jwt: token }); - } -} - -async fn get_access_token(oauth_code: &str, state: &State) -> Option { - #[derive(Serialize)] - struct TokenForm<'a> { - code: &'a str, - client_id: String, - client_secret: String, - redirect_uri: String, - grant_type: &'static str, - } - - #[derive(Deserialize)] - #[allow(unused)] - struct TokenResponse { - access_token: String, - expires_in: u32, - scope: String, - token_type: String, - id_token: String, - } - - let token_json = state - .reqwest_client - .post(GOOGLE_TOKEN_URL) - .form(&TokenForm { - code: oauth_code, - client_id: dotenv::var("GOOGLE_CLIENT_ID").expect("GOOGLE_CLIENT_ID should be in env"), - client_secret: dotenv::var("GOOGLE_CLIENT_SECRET") - .expect("GOOGLE_CLIENT_SECRET should be in env"), - redirect_uri: dotenv::var("GOOGLE_REDIRECT_URI") - .expect("GOOGLE_CLIENT_SECRET should be in env"), - grant_type: "authorization_code", - }) - .send() - .await - .map_err(|e| eprintln!("Oauth request failed: {}", e)) - .ok()? - .json::() - .await - .ok()?; - - match serde_json::from_value::(token_json.clone()) { - Ok(t) => Some(t.access_token), - Err(e) => { - eprintln!( - "Failed to parse token response: {}\nJSON is {}", - e, token_json - ); - None - } - } -} - -struct UserDetails { - email: Option, - name: Option, -} - -async fn get_user_details(state: &State, token: &str) -> UserDetails { - let response = match state - .reqwest_client - .get(&state.userinfo_endpoint) - .header(header::AUTHORIZATION, format!("Bearer {}", token)) - .send() - .await - { - Ok(response) => response, - Err(_) => { - return UserDetails { - email: None, - name: None, - } - } - }; - - let mut user_info = match response.json::().await { - Ok(user_info) => user_info, - Err(_) => { - return UserDetails { - email: None, - name: None, - } - } - }; - - let email = match user_info.get_mut(OPENID_EMAIL_FIELD) { - Some(Value::String(email)) => Some(email.to_string()), - _ => None, - }; - - let name = match user_info.get_mut(OPENID_NAME_FIELD) { - Some(Value::String(name)) => Some(name.to_string()), - _ => None, - }; - - UserDetails { email, name } -} - -#[derive(Deserialize)] -pub struct SignInBody { - oauth_token: String, -} - -#[derive(Serialize)] -pub struct SignInResponse { - token: String, - name: String, -} - -#[derive(Serialize)] -pub enum SignInError { - InvalidOAuthCode, - GoogleOAuthInternalError, - SignupRequired { - signup_token: String, - name: Option, - }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SignupJwt { - pub auth_token: String, -} - -#[post("/signin", data = "")] -pub async fn signin( - body: Json, - state: &State, - db: Database, -) -> Result, JsonErr> { - let token = get_access_token(&body.oauth_token, state) - .await - .ok_or_else(|| { - eprintln!( - "Failed to get access token for oauth token {}", - body.oauth_token - ); - JsonErr(SignInError::InvalidOAuthCode, Status::Forbidden) - })?; - - let details = get_user_details(state, &token).await; - - let email = details.email.ok_or(JsonErr( - SignInError::GoogleOAuthInternalError, - Status::Forbidden, - ))?; - - let user = db - .run(move |conn| User::get_from_email(conn, &email)) - .await - .ok_or_else(|| { - let jwt = SignupJwt { auth_token: token }; - - let token = jsonwebtoken::encode( - &Header::new(jsonwebtoken::Algorithm::HS256), - &jwt, - &state.jwt_encoding_key, - ) - .expect("creating jwt should never fail"); - - JsonErr( - SignInError::SignupRequired { - signup_token: token, - name: details.name, - }, - Status::Ok, - ) - })?; - - let auth = AuthJwt { - user_id: user.id as u32, - }; - - let token = jsonwebtoken::encode( - &Header::new(jsonwebtoken::Algorithm::HS256), - &auth, - &state.jwt_encoding_key, - ) - .expect("creating jwt should never fail"); - - Ok(Json(SignInResponse { - token, - name: user.display_name, - })) -} - -#[derive(Deserialize, Clone)] -pub struct SignUpBody { - signup_token: String, - zid: String, - display_name: String, - degree_name: String, - degree_starting_year: u32, - gender: UserGender, - pronouns: String, -} - -#[derive(Serialize)] -pub struct SignUpResponse { - token: String, -} - -#[derive(Serialize)] -pub enum SignUpError { - InvalidSignupToken, - GoogleOAuthInternalError, - AccountAlreadyExists, -} - -#[post("/signup", data = "")] -pub async fn signup( - body: Json, - state: &State, - db: Database, -) -> Result, JsonErr> { - let mut validation = Validation::new(Algorithm::HS256); - validation.validate_exp = false; - validation.required_spec_claims.clear(); - - let token = match jsonwebtoken::decode::( - &body.signup_token, - &state.jwt_decoding_key, - &validation, - ) { - Ok(data) => data.claims.auth_token, - Err(_) => { - return Err(JsonErr( - SignUpError::InvalidSignupToken, - Status::FailedDependency, - )) - } - }; - - let details = get_user_details(state, &token).await; - - let email = details.email.ok_or(JsonErr( - SignUpError::GoogleOAuthInternalError, - Status::FailedDependency, - ))?; - - { - let email = email.clone(); - - if db - .run(move |conn| User::get_from_email(conn, &email)) - .await - .is_some() - { - return Err(JsonErr(SignUpError::AccountAlreadyExists, Status::ImUsed)); - } - } - - let user = { - let email = email.clone(); - let body = body.clone(); - - db.run(move |conn| { - let user = NewUser { - email, - zid: body.zid.to_string(), - display_name: body.display_name.to_string(), - degree_name: body.degree_name.to_string(), - degree_starting_year: body.degree_starting_year as i32, - gender: body.gender, - pronouns: body.pronouns.to_string(), - superuser: User::get_number(conn) == 0, - }; - - user.insert(conn) - .expect("we already ensured a conflicting user does not exist") - }) - .await - }; - - let auth = AuthJwt { - user_id: user.id as u32, - }; - - let token = jsonwebtoken::encode( - &Header::new(jsonwebtoken::Algorithm::HS256), - &auth, - &state.jwt_encoding_key, - ) - .expect("creating jwt should never fail"); - - Ok(Json(SignUpResponse { token })) -} diff --git a/backend/server/src/bin.rs b/backend/server/src/bin.rs deleted file mode 100644 index f1546557..00000000 --- a/backend/server/src/bin.rs +++ /dev/null @@ -1,170 +0,0 @@ -extern crate diesel; - -#[macro_use] -extern crate diesel_migrations; - -use backend::cors::cors; -use backend::database::Database; -use backend::{auth::Auth, images::IMAGE_BASE_PATH}; -use diesel::pg::PgConnection; -use diesel::prelude::*; -use diesel_migrations::*; -use figment::{providers::Serialized, Figment}; -use rocket::{routes, serde::json::Value}; -use std::{env, fs, path::Path}; - -#[rocket::get("/foo")] -fn authed_call(auth: Auth) -> String { - format!("hello, your user id is {}", auth.jwt.user_id) -} - -embed_migrations!(); - -#[rocket::main] -async fn main() { - dotenv::dotenv().ok(); - - let db_url = run_migrations(); - - let api_state = backend::state::api_state().await; - - let cors = cors(); - - let config_map: Value = serde_json::from_str(&format!( - r#"{{ - "databases": {{ - "database": {{ - "url": "{}" - }} - }}, - "log_level": "debug", - "address": "0.0.0.0", - "limits": {{ - "json": "10485760" - }} - }}"#, - db_url - )) - .unwrap(); - - let figment = Figment::from(rocket::Config::default()).merge(Serialized::globals(config_map)); - - // create images dir if not found - fs::create_dir_all(Path::new(IMAGE_BASE_PATH)).ok(); - - let _ = rocket::custom(figment) - .manage(api_state) - .attach(Database::fairing()) - .attach(cors) - .mount("/", routes![authed_call]) - .mount( - "/organisation", - routes![ - backend::organisation::new, - backend::organisation::get_from_id, - backend::organisation::delete, - backend::organisation::get_admins, - backend::organisation::set_admins, - backend::organisation::is_admin, - backend::organisation::get_from_ids, - backend::organisation::invite_uid, - backend::organisation::invite_email, - backend::organisation::set_logo, - ], - ) - .mount( - "/auth", - routes![backend::auth::signin, backend::auth::signup], - ) - .mount( - "/campaign", - routes![ - backend::campaigns::get, - backend::campaigns::update, - backend::campaigns::roles, - backend::campaigns::new, - backend::campaigns::delete_campaign, - backend::campaigns::get_all_campaigns, - backend::campaigns::set_cover_image, - ], - ) - .mount( - "/user", - routes![ - backend::user::get_user, - backend::user::get_user_campaigns, - backend::user::get, - ], - ) - .mount( - "/application", - routes![ - backend::application::create_application, - backend::application::create_rating, - backend::application::submit_answer, - backend::application::get_answers, - backend::application::get_ratings, - backend::application::set_status, - backend::application::set_private_status, - backend::application::get_comments, - ], - ) - .mount( - "/role", - routes![ - backend::role::get_role, - backend::role::update_role, - backend::role::new_role, - backend::role::get_questions, - backend::role::get_applications, - ], - ) - .mount( - "/comment", - routes![ - backend::comment::create_comment, - backend::comment::get_comment_from_id - ], - ) - .mount( - "/question", - routes![ - backend::question::get_question, - backend::question::edit_question, - backend::question::delete_question - ], - ) - .mount( - "/admin", - routes![backend::admin::get, backend::admin::make_superuser], - ) - .mount("/static", routes![backend::static_resources::files]) - .launch() - .await - .unwrap(); -} - -fn run_migrations() -> String { - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - - assert!(&database_url[database_url.len() - 5..] == "chaos"); - - let database_url_no_chaos = String::from(&database_url[..database_url.len() - 5]); - - let main_connection = PgConnection::establish(&database_url_no_chaos) - .expect(&format!("Error connecting to {}", database_url_no_chaos)); - - match diesel::sql_query("CREATE DATABASE chaos").execute(&main_connection) { - _ => (), - }; - - let chaos_connection = PgConnection::establish(&database_url) - .expect(&format!("Error connecting to {}", database_url)); - - embedded_migrations::run_with_output(&chaos_connection, &mut std::io::stdout()) - .expect("Failed to run migrations"); - - println!("Finishing running migrations"); - - return database_url; -} diff --git a/backend/server/src/campaigns.rs b/backend/server/src/campaigns.rs deleted file mode 100644 index ca64a8eb..00000000 --- a/backend/server/src/campaigns.rs +++ /dev/null @@ -1,308 +0,0 @@ -use crate::error::JsonErr; -use crate::images::get_image_path; -use crate::{ - database::{ - models::{ - Campaign, CampaignWithRoles, NewCampaignInput, NewQuestion, OrganisationUser, Role, - RoleUpdate, UpdateCampaignInput, User, - }, - Database, - }, - images::{get_http_image_path, save_image, try_decode_data, ImageLocation}, -}; -use rocket::{data::Data, delete, get, http::Status, post, put, serde::json::Json}; -use serde::{Deserialize, Serialize}; -use std::fs::remove_file; - -use uuid::Uuid; - -#[derive(Serialize)] -pub enum CampaignError { - CampaignNotFound, - Unauthorized, - UnableToCreate, - InvalidInput, -} - -#[get("/")] -pub async fn get(campaign_id: i32, db: Database) -> Result, JsonErr> { - let mut campaign = db - .run(move |conn| Campaign::get_from_id(conn, campaign_id)) - .await; - - campaign = campaign.map(|mut campaign| { - campaign.cover_image = campaign - .cover_image - .map(|logo_uuid| get_http_image_path(ImageLocation::ORGANISATIONS, &logo_uuid)); - campaign - }); - - match campaign { - Some(campaign) => { - if campaign.published { - Ok(Json(campaign)) - } else { - Err(JsonErr(CampaignError::Unauthorized, Status::Forbidden)) - } - } - None => Err(JsonErr(CampaignError::CampaignNotFound, Status::NotFound)), - } -} - -#[derive(Serialize)] -pub struct DashboardCampaignGroupings { - pub current_campaigns: Vec, - pub past_campaigns: Vec, -} - -#[get("/all")] -pub async fn get_all_campaigns(user: User, db: Database) -> Json { - fn with_http_cover_images(campaigns: Vec) -> Vec { - campaigns - .into_iter() - .map(CampaignWithRoles::with_http_cover_image) - .collect() - } - - let (current_campaigns, past_campaigns) = db - .run(move |conn| { - ( - with_http_cover_images(Campaign::get_all_public_with_roles(conn, user.id)), - with_http_cover_images(Campaign::get_all_public_ended_with_roles(conn, user.id)), - ) - }) - .await; - - Json(DashboardCampaignGroupings { - current_campaigns, - past_campaigns, - }) -} - -#[put("/", data = "")] -pub async fn update( - campaign_id: i32, - update_campaign: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - OrganisationUser::campaign_admin_level(campaign_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(JsonErr(CampaignError::Unauthorized, Status::Forbidden)))?; - - Campaign::update(conn, campaign_id, &update_campaign); - - Ok(Json(())) - }) - .await -} - -#[derive(Serialize, Deserialize)] -pub struct RoleInput { - pub name: String, - pub description: Option, - pub min_available: i32, - pub max_available: i32, - pub questions_for_role: Vec, -} - -fn default_max_bytes() -> i32 { - 100 -} - -#[derive(Serialize, Deserialize)] -pub struct QuestionInput { - pub title: String, - pub description: Option, - #[serde(default = "default_max_bytes")] - pub max_bytes: i32, - #[serde(default)] - pub required: bool, -} - -#[derive(Deserialize)] -pub struct NewCampaignWithData { - pub campaign: NewCampaignInput, - pub roles: Vec, - pub questions: Vec, -} - -#[post("/", data = "")] -pub async fn new( - new_campaign: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - let inner = new_campaign.into_inner(); - let NewCampaignWithData { - campaign, - roles, - questions, - } = inner; - - let mut new_questions: Vec = questions - .into_iter() - .map(|x| NewQuestion { - role_ids: vec![], - title: x.title, - description: x.description, - max_bytes: x.max_bytes, - required: x.required, - }) - .collect(); - - db.run(move |conn| { - OrganisationUser::organisation_admin_level(campaign.organisation_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(JsonErr(CampaignError::Unauthorized, Status::Forbidden)))?; - - let campaign = Campaign::create(conn, &campaign).ok_or_else(|| { - eprintln!("Failed to create campaign for some reason: {:?}", campaign); - JsonErr(CampaignError::UnableToCreate, Status::InternalServerError) - })?; - - for role in roles { - let new_role = RoleUpdate { - campaign_id: campaign.id, - name: role.name, - description: role.description, - min_available: role.min_available, - max_available: role.max_available, - finalised: campaign.published, - }; - let inserted_role = new_role.insert(conn).ok_or_else(|| { - eprintln!("Failed to create role for some reason: {:?}", new_role); - JsonErr(CampaignError::UnableToCreate, Status::InternalServerError) - })?; - - for question in role.questions_for_role { - if question < new_questions.len() { - new_questions[question].role_ids.push(inserted_role.id); - } - } - } - - for question in new_questions { - if question.role_ids.len() == 0 { - return Err(JsonErr(CampaignError::InvalidInput, Status::BadRequest)); - } - question.insert(conn).ok_or_else(|| { - eprintln!("Failed to create question for some reason"); - JsonErr(CampaignError::UnableToCreate, Status::InternalServerError) - })?; - } - - Ok(Json(campaign)) - }) - .await -} - -#[delete("/")] -pub async fn delete_campaign( - campaign_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - // need to be admin to create new campaign - OrganisationUser::campaign_admin_level(campaign_id, user.id, &conn) - .is_admin() - .check() - .or_else(|_| Err(JsonErr(CampaignError::Unauthorized, Status::Forbidden)))?; - - Campaign::delete_deep(conn, campaign_id); - - Ok(Json(())) - }) - .await -} - -#[derive(Serialize)] -pub struct RolesResponse { - roles: Vec, -} - -#[derive(Serialize)] -pub enum RolesError { - CampaignNotFound, - Unauthorized, - RoleAlreadyExists, -} - -#[get("//roles")] -pub async fn roles( - campaign_id: i32, // campaign_id has namespace conflict - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let campaign = Campaign::get_from_id(conn, campaign_id) - .ok_or_else(|| JsonErr(RolesError::CampaignNotFound, Status::NotFound))?; - - OrganisationUser::campaign_admin_level(campaign_id, user.id, &conn) - .is_at_least_director() - .or(campaign.published) // only if not (read only and campaign is unpublished) - .check() - .or_else(|_| Err(JsonErr(RolesError::Unauthorized, Status::Forbidden)))?; - - let roles = Role::get_all_from_campaign_id(conn, campaign.id); - - Ok(Json(RolesResponse { roles })) - }) - .await -} - -#[derive(Serialize)] -pub enum LogoError { - Unauthorized, - ImageDeletionFailure, - ImageStoreFailure, -} - -#[put("//cover_image", data = "")] -pub async fn set_cover_image( - campaign_id: i32, - user: User, - db: Database, - image: Data<'_>, -) -> Result, JsonErr> { - db.run(move |conn| { - OrganisationUser::campaign_admin_level(campaign_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(JsonErr(LogoError::Unauthorized, Status::Forbidden))) - }) - .await?; - - let old_logo_uuid = db - .run(move |conn| Campaign::get_cover_image(&conn, campaign_id)) - .await; - let logo_uuid = Uuid::new_v4().as_hyphenated().to_string() + ".webp"; - - let image = try_decode_data(image).await.or_else(|_| { - Err(JsonErr( - LogoError::ImageDeletionFailure, - Status::InternalServerError, - )) - })?; - - save_image(image, ImageLocation::CAMPAIGNS, &logo_uuid) - .map_err(|_| JsonErr(LogoError::ImageStoreFailure, Status::InternalServerError))?; - - let logo_uuid_clone = logo_uuid.clone(); - - db.run(move |conn| Campaign::set_cover_image(&conn, campaign_id, &logo_uuid_clone)) - .await; - - if let Some(uuid) = old_logo_uuid { - remove_file(get_image_path(ImageLocation::CAMPAIGNS, &uuid)).ok(); - } - - Ok(Json(get_http_image_path( - ImageLocation::CAMPAIGNS, - &logo_uuid, - ))) -} diff --git a/backend/server/src/comment.rs b/backend/server/src/comment.rs deleted file mode 100644 index bd123cd4..00000000 --- a/backend/server/src/comment.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::database::{ - models::{Comment, NewComment, OrganisationUser, User}, - Database, -}; -use crate::error::JsonErr; -use rocket::{ - get, - http::Status, - put, - serde::{json::Json, Deserialize, Serialize}, -}; - -#[derive(Deserialize)] -pub struct NewCommentInput { - pub application_id: i32, - pub description: String, -} - -#[derive(Serialize)] -pub enum CommentError { - Unauthorized, - CouldNotInsert, - CommentNotFound, -} - -#[put("/", data = "")] -pub async fn create_comment( - new_comment_input: Json, - user: User, - db: Database, -) -> Result, JsonErr> { - // need to be director to comment - let app_id = new_comment_input.application_id; // stack copy of i32 - db.run(move |conn| { - OrganisationUser::application_admin_level(app_id, user.id, &conn) - .is_at_least_director() - .check() - }) - .await - .or_else(|_| Err(JsonErr(CommentError::Unauthorized, Status::Forbidden)))?; - - let new_comment = NewComment { - application_id: new_comment_input.application_id, - commenter_user_id: user.id, - description: new_comment_input.description.to_string(), - }; - let comment = db - .run(move |conn| NewComment::insert(&new_comment, conn)) - .await - .ok_or_else(|| JsonErr(CommentError::CouldNotInsert, Status::InternalServerError))?; - - Ok(Json(comment)) -} - -#[get("/")] -pub async fn get_comment_from_id( - comment_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - OrganisationUser::comment_admin_level(comment_id, user.id, &conn) - .is_at_least_director() - .check() - }) - .await - .or_else(|_| Err(JsonErr(CommentError::Unauthorized, Status::Forbidden)))?; - - let comment = db - .run(move |conn| Comment::get_from_id(conn, comment_id)) - .await - .ok_or_else(|| JsonErr(CommentError::CommentNotFound, Status::NotFound))?; - - Ok(Json(comment)) -} diff --git a/backend/server/src/cors.rs b/backend/server/src/cors.rs deleted file mode 100644 index 12db39f5..00000000 --- a/backend/server/src/cors.rs +++ /dev/null @@ -1,21 +0,0 @@ -use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; - -pub fn cors() -> Cors { - let cors = rocket_cors::CorsOptions { - allowed_origins: AllowedOrigins::All, - allowed_methods: { - // why do i have to do this, honestly - use rocket::http::Method::*; - vec![Get, Put, Post, Delete, Options, Head, Trace, Connect, Patch] - .into_iter() - .map(From::from) - .collect() - }, - allowed_headers: AllowedHeaders::All, - ..Default::default() - } - .to_cors() - .expect("Failed to create CORS options"); - - cors -} diff --git a/backend/server/src/database/mod.rs b/backend/server/src/database/mod.rs deleted file mode 100644 index 97015d39..00000000 --- a/backend/server/src/database/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod models; -pub mod schema; - -use rocket_sync_db_pools::database; - -#[database("database")] -pub struct Database(diesel::PgConnection); diff --git a/backend/server/src/database/models.rs b/backend/server/src/database/models.rs deleted file mode 100644 index d4236081..00000000 --- a/backend/server/src/database/models.rs +++ /dev/null @@ -1,1425 +0,0 @@ -use crate::images::{get_http_image_path, ImageLocation}; - -use super::schema::{ - answers, applications, campaigns, comments, organisation_users, organisations, questions, - ratings, roles, users, -}; -use super::schema::{AdminLevel, ApplicationStatus, UserGender}; -use chrono::NaiveDateTime; -use chrono::Utc; -use diesel::prelude::*; -use diesel::PgConnection; -use rocket::FromForm; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use std::fs::remove_file; -use std::path::Path; - -#[derive(Queryable)] -pub struct User { - pub id: i32, - pub email: String, - pub zid: String, - pub display_name: String, - pub degree_name: String, - pub degree_starting_year: i32, - pub gender: UserGender, - pub pronouns: String, - pub superuser: bool, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -pub struct SuperUser { - user: User, -} - -impl SuperUser { - pub fn new(user: User) -> Self { - Self { user } - } - - pub fn user(&self) -> &User { - &self.user - } -} -#[derive(Insertable)] -#[table_name = "users"] -pub struct NewUser { - pub email: String, - pub zid: String, - pub display_name: String, - pub degree_name: String, - pub degree_starting_year: i32, - pub gender: UserGender, - pub pronouns: String, - pub superuser: bool, -} - -impl User { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::users::dsl::*; - - users.order(id.asc()).load(conn).unwrap_or_else(|_| vec![]) - } - - pub fn make_superuser(&self, conn: &PgConnection) -> Result<(), diesel::result::Error> { - use crate::database::schema::users::dsl::*; - - diesel::update(users.filter(id.eq(self.id))) - .set(superuser.eq(true)) - .execute(conn)?; - - Ok(()) - } - - pub fn get_number(conn: &PgConnection) -> i64 { - use crate::database::schema::users::dsl::*; - use diesel::dsl::count; - - users - .select(count(display_name)) - .first(conn) - .unwrap_or_else(|_| 0) - } - - pub fn get_from_id(conn: &PgConnection, id_val: i32) -> Option { - use crate::database::schema::users::dsl::*; - - users.filter(id.eq(id_val)).first(conn).ok() - } - - pub fn get_from_email(conn: &PgConnection, user_email: &str) -> Option { - use crate::database::schema::users::dsl::*; - - users.filter(email.eq(user_email)).first(conn).ok() - } - - pub fn get_all_campaigns(&self, conn: &PgConnection) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - campaigns - .filter(organisation_id.eq(self.id)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_org_ids_belonging(&self, conn: &PgConnection) -> Vec { - if self.superuser { - return organisations::table - .select(organisations::id) - .load::(conn) - .unwrap_or_else(|_| vec![]); - } - - organisation_users::table - .filter(organisation_users::user_id.eq(self.id)) - .select(organisation_users::organisation_id) - .load::(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_orgs_belonging(&self, conn: &PgConnection) -> Vec { - if self.superuser { - return organisations::table - .load::(conn) - .unwrap_or_else(|_| vec![]); - } - - organisation_users::table - .filter(organisation_users::user_id.eq(self.id)) - .inner_join( - organisations::table.on(organisations::id.eq(organisation_users::organisation_id)), - ) - .select(( - organisations::id, - organisations::name, - organisations::logo, - organisations::created_at, - organisations::updated_at, - )) - .load::(conn) - .unwrap_or_else(|_| vec![]) - } -} - -impl NewUser { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::users::dsl::*; - - self.insert_into(users).get_result(conn).ok() - } -} - -#[derive(Queryable, Serialize, Deserialize)] -pub struct Organisation { - pub id: i32, - pub name: String, - pub logo: Option, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable, FromForm, Deserialize)] -#[table_name = "organisations"] -pub struct NewOrganisation { - pub name: String, - pub logo: Option, -} - -impl Organisation { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::organisations::dsl::*; - - organisations - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_from_id(conn: &PgConnection, organisation_id: i32) -> Option { - use crate::database::schema::organisations::dsl::*; - - organisations - .filter(id.eq(organisation_id)) - .first(conn) - .ok() - } - - pub fn delete(conn: &PgConnection, organisation_id: i32) -> Option { - use crate::database::schema::organisations::dsl::*; - - let num = diesel::delete(organisations.filter(id.eq(organisation_id))) - .execute(conn) - .ok()?; - - if num > 0 { - Some(num) - } else { - None - } - } - - pub fn delete_deep(conn: &PgConnection, org_id: i32) -> Option<()> { - use crate::database::schema::organisation_users::dsl::*; - if let Some(logo) = Organisation::get_logo(conn, org_id) { - remove_file(Path::new(&logo)).ok(); - } - - let campaigns = Campaign::get_all_from_org_id(conn, org_id); - - for campaign in campaigns { - Campaign::delete_deep(conn, campaign.id)?; - } - - diesel::delete(organisation_users.filter(organisation_id.eq(org_id))) - .execute(conn) - .ok(); - - let num = Organisation::delete(conn, org_id)?; - - if num < 1 { - return None; - } - - Some(()) - } - - pub fn find_by_name(conn: &PgConnection, organisation_name: &str) -> Option { - use crate::database::schema::organisations::dsl::*; - - organisations - .filter(name.eq(organisation_name)) - .first(conn) - .ok() - } - - pub fn get_admin_ids(conn: &PgConnection, org_id: i32) -> Option> { - organisation_users::table - .filter(organisation_users::organisation_id.eq(org_id)) - .load::(conn) - .map(|org_users| { - org_users - .into_iter() - .filter(|org_user| org_user.admin_level == AdminLevel::Admin) - .map(|org_user| org_user.user_id) - .collect() - }) - .ok() - } - - // FIXME - rather than looping through all admins, filter the users if theyre in admin_ids - // FIXME - this only works if they're already in the organisation, need to insert them into the - // organistaion first? - pub fn set_admins(conn: &PgConnection, org_id: i32, admin_ids: &[i32]) -> Option { - use crate::database::schema::organisation_users::dsl::*; - - diesel::update( - organisation_users - .filter(organisation_id.eq(org_id)) - .filter(user_id.eq_any(admin_ids)), - ) - .set(admin_level.eq(AdminLevel::Admin)) - .execute(conn) - .ok() - } - - pub fn get_logo(conn: &PgConnection, org_id: i32) -> Option { - use crate::database::schema::organisations::dsl::*; - - organisations - .filter(id.eq(org_id)) - .select(logo) - .first(conn) - .unwrap() - } - - pub fn set_logo(conn: &PgConnection, org_id: i32, new_logo: &str) -> String { - use crate::database::schema::organisations::dsl::*; - - diesel::update(organisations.find(org_id)) - .set(logo.eq(new_logo)) - .get_result::(conn) - .unwrap() - .logo - .unwrap() - } -} - -impl NewOrganisation { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::organisations::dsl::*; - - self.insert_into(organisations).get_result(conn).ok() - } -} - -#[derive(Queryable, Associations)] -#[belongs_to(Organisation)] -#[belongs_to(User)] -pub struct OrganisationUser { - pub id: i32, - pub user_id: i32, - pub organisation_id: i32, - pub admin_level: AdminLevel, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable)] -#[table_name = "organisation_users"] -pub struct NewOrganisationUser { - pub user_id: i32, - pub organisation_id: i32, - pub admin_level: AdminLevel, -} - -impl OrganisationUser { - pub fn get( - conn: &PgConnection, - organisation_id_val: i32, - user_id_val: i32, - ) -> Option { - use crate::database::schema::organisation_users::dsl::*; - - organisation_users - .filter(organisation_id.eq(organisation_id_val)) - .filter(user_id.eq(user_id_val)) - .first(conn) - .ok() - } - - pub fn update_admin_level( - &self, - conn: &PgConnection, - admin_level_val: AdminLevel, - ) -> Option<()> { - use crate::database::schema::organisation_users::dsl::*; - - diesel::update( - organisation_users - .filter(organisation_id.eq(self.organisation_id)) - .filter(user_id.eq(self.user_id)), - ) - .set(admin_level.eq(admin_level_val)) - .execute(conn) - .map(|_| ()) - .ok() - } - - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::organisation_users::dsl::*; - - organisation_users - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_user_id(conn: &PgConnection, user_id_val: i32) -> Vec { - use crate::database::schema::organisation_users::dsl::*; - - organisation_users - .filter(user_id.eq(user_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_organisation_id( - conn: &PgConnection, - organisation_id_val: i32, - ) -> Vec { - use crate::database::schema::organisation_users::dsl::*; - - organisation_users - .filter(organisation_id.eq(organisation_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } -} - -impl NewOrganisationUser { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::organisation_users::dsl::*; - - self.insert_into(organisation_users).get_result(conn).ok() - } -} - -#[derive(Queryable, Serialize, Debug, Associations)] -#[belongs_to(Organisation)] -pub struct Campaign { - pub id: i32, - pub organisation_id: i32, - pub name: String, - pub cover_image: Option, - pub description: String, - pub starts_at: NaiveDateTime, - pub ends_at: NaiveDateTime, - pub published: bool, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(FromForm, Deserialize)] -pub struct UpdateCampaignInput { - pub name: String, - pub cover_image: Option, - pub description: String, - pub starts_at: String, - pub ends_at: String, - pub published: bool, -} - -#[derive(AsChangeset)] -#[table_name = "campaigns"] -pub struct UpdateCampaignChangeset { - pub name: String, - pub cover_image: Option, - pub description: String, - pub starts_at: NaiveDateTime, - pub ends_at: NaiveDateTime, - pub published: bool, -} - -#[derive(Insertable, Debug)] -#[table_name = "campaigns"] -pub struct NewCampaign { - pub organisation_id: i32, - pub name: String, - pub cover_image: Option, - pub description: String, - pub starts_at: NaiveDateTime, - pub ends_at: NaiveDateTime, - pub published: bool, -} - -#[derive(Deserialize, Clone, Debug)] -pub struct NewCampaignInput { - pub organisation_id: i32, - pub name: String, - pub description: String, - pub starts_at: String, - pub ends_at: String, - pub published: bool, -} - -#[derive(Serialize)] -pub struct CampaignWithRoles { - pub campaign: Campaign, - pub roles: Vec, - pub questions: Vec, - pub applied_for: Vec<(i32, ApplicationStatus)>, -} - -impl CampaignWithRoles { - pub fn with_http_cover_image(mut self) -> Self { - self.campaign = self.campaign.with_http_cover_image(); - self - } -} - -impl Campaign { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - campaigns - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_cover_image(conn: &PgConnection, campaign_id: i32) -> Option { - use crate::database::schema::campaigns::dsl::*; - - campaigns - .filter(id.eq(campaign_id)) - .select(cover_image) - .first(conn) - .unwrap() - } - - pub fn with_http_cover_image(mut self) -> Self { - self.cover_image = self - .cover_image - .map(|logo_uuid| get_http_image_path(ImageLocation::CAMPAIGNS, &logo_uuid)); - self - } - - pub fn set_cover_image(conn: &PgConnection, campaign_id: i32, new_cover_image: &str) -> String { - use crate::database::schema::campaigns::dsl::*; - - diesel::update(campaigns.find(campaign_id)) - .set(cover_image.eq(new_cover_image)) - .get_result::(conn) - .unwrap() - .cover_image - .unwrap() - } - - pub fn is_running(&self) -> bool { - let now = Utc::now().naive_utc(); - self.starts_at <= now && self.ends_at >= now - } - - /// return all campaigns that are live to all users - pub fn get_all_public_with_roles(conn: &PgConnection, user_id: i32) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - let now = Utc::now().naive_utc(); - let campaigns_vec: Vec = campaigns - .filter( - starts_at - .le(now) - .and(published.eq(true)) - .and(ends_at.gt(now)), - ) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]); - - Self::pack_roles_and_applied_to_into_campaigns_vec(conn, campaigns_vec, user_id) - } - - fn pack_roles_and_applied_to_into_campaigns_vec( - conn: &PgConnection, - campaigns_vec: Vec, - user_id: i32, - ) -> Vec { - campaigns_vec - .into_iter() - .map(|campaign| { - let campaign_roles = Role::get_all_from_campaign_id(&conn, campaign.id); - - let mut seen = HashSet::new(); - - let questions = campaign_roles - .iter() - .map(|x| Question::get_all_from_role_id(conn, x.id).into_iter()) - .flatten() - .filter(|x| { - if !seen.contains(&x.id) { - seen.insert(x.id); - true - } else { - false - } - }) - .collect(); - - let applied_for: Vec<(i32, ApplicationStatus)> = campaign_roles - .clone() - .into_iter() - .filter_map(|role| { - let app = Application::get_all_from_role_id(&conn, role.id) - .into_iter() - .filter(|app| app.user_id == user_id) - .next()?; - Some((role.id, app.status)) - }) - .collect(); - - CampaignWithRoles { - campaign, - roles: campaign_roles, - applied_for, - questions, - } - }) - .collect() - } - - // return all campaigns that are live and in the past - pub fn get_all_public_ended_with_roles( - conn: &PgConnection, - user_id: i32, - ) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - let now = Utc::now().naive_utc(); - let campaigns_vec: Vec = campaigns - .filter(ends_at.lt(now).and(published.eq(true))) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]); - - Self::pack_roles_and_applied_to_into_campaigns_vec(conn, campaigns_vec, user_id) - } - - pub fn get_all_from_org_id(conn: &PgConnection, organisation_id_val: i32) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - campaigns - .filter(organisation_id.eq(organisation_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_from_organisation_id( - conn: &PgConnection, - organisation_id_val: i32, - ) -> Vec { - use crate::database::schema::campaigns::dsl::*; - - campaigns - .filter(organisation_id.eq(organisation_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_from_id(conn: &PgConnection, campaign_id: i32) -> Option { - use crate::database::schema::campaigns::dsl::*; - - campaigns.filter(id.eq(campaign_id)).first(conn).ok() - } - - pub fn update( - conn: &PgConnection, - campaign_id: i32, - update_campaign: &UpdateCampaignInput, - ) -> Option { - use crate::database::schema::campaigns::dsl::*; - - let update_changeset = UpdateCampaignChangeset { - name: update_campaign.name.clone(), - cover_image: update_campaign.cover_image.clone(), - description: update_campaign.description.clone(), - starts_at: NaiveDateTime::parse_from_str( - &update_campaign.starts_at, - "%Y-%m-%dT%H:%M:%S", - ) - .unwrap(), - ends_at: NaiveDateTime::parse_from_str(&update_campaign.ends_at, "%Y-%m-%dT%H:%M:%S") - .unwrap(), - published: update_campaign.published, - }; - - diesel::update(campaigns.filter(id.eq(campaign_id))) - .set(update_changeset) - .get_result(conn) - .ok() - } - - pub fn create(conn: &PgConnection, new_campaign: &NewCampaignInput) -> Option { - use crate::database::schema::campaigns::dsl::*; - - let new_campaign = NewCampaign { - organisation_id: new_campaign.organisation_id, - name: new_campaign.name.clone(), - cover_image: None, - description: new_campaign.description.clone(), - starts_at: NaiveDateTime::parse_from_str(&new_campaign.starts_at, "%Y-%m-%d %H:%M:%S") - .ok()?, - ends_at: NaiveDateTime::parse_from_str(&new_campaign.ends_at, "%Y-%m-%d %H:%M:%S") - .ok()?, - published: new_campaign.published, - }; - - if campaigns - .filter(organisation_id.eq(new_campaign.organisation_id)) - .filter(name.eq(&new_campaign.name)) - .first::(conn) - .is_ok() - { - return None; - } - - new_campaign.insert(conn) - } - - pub fn delete(conn: &PgConnection, campaign_id: i32) -> bool { - use crate::database::schema::campaigns::dsl::*; - - diesel::delete(campaigns.filter(id.eq(campaign_id))) - .execute(conn) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, campaign_id: i32) -> Option<()> { - use crate::database::schema::roles::dsl::{campaign_id as dsl_role_campaign_id, roles}; - - if let Some(cover_image) = Campaign::get_cover_image(conn, campaign_id) { - remove_file(Path::new(&cover_image)).ok()?; - } - - let role_items: Vec = roles - .filter(dsl_role_campaign_id.eq(campaign_id)) - .load(conn) - .map_err(|x| eprintln!("error in delete deep: {x:?}")) - .ok()?; - - role_items.into_iter().for_each(|role| { - Role::delete_deep(conn, role.id); - }); - - if !Campaign::delete(conn, campaign_id) { - None - } else { - Some(()) - } - } -} - -impl NewCampaign { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::campaigns::dsl::*; - self.insert_into(campaigns).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, Serialize, Associations, Clone, PartialEq)] -#[belongs_to(Campaign)] -pub struct Role { - pub id: i32, - pub campaign_id: i32, - pub name: String, - pub description: Option, - pub min_available: i32, - pub max_available: i32, - pub finalised: bool, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, FromForm, Deserialize, Debug)] -#[table_name = "roles"] -pub struct RoleUpdate { - pub campaign_id: i32, - pub name: String, - pub description: Option, - pub min_available: i32, - pub max_available: i32, - pub finalised: bool, -} - -impl Role { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::roles::dsl::*; - - roles.order(id.asc()).load(conn).unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_campaign_id(conn: &PgConnection, campaign_id_val: i32) -> Vec { - use crate::database::schema::roles::dsl::*; - - roles - .filter(campaign_id.eq(campaign_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_from_name(conn: &PgConnection, role_name: &str) -> Option { - use crate::database::schema::roles::dsl::*; - - roles.filter(name.eq(role_name)).first(conn).ok() - } - - pub fn get_from_id(conn: &PgConnection, role_id: i32) -> Option { - use crate::database::schema::roles::dsl::*; - - roles.filter(id.eq(role_id)).first(conn).ok() - } - - pub fn update(conn: &PgConnection, role_id: i32, role_update: &RoleUpdate) -> Option { - use crate::database::schema::roles::dsl::*; - - diesel::update(roles.filter(id.eq(role_id))) - .set(role_update) - .get_result(conn) - .ok() - } - - pub fn delete(conn: &PgConnection, role_id: i32) -> bool { - use crate::database::schema::roles::dsl::*; - - diesel::delete(roles.filter(id.eq(role_id))) - .execute(conn) - .is_ok() - } - - pub fn delete_children(conn: &PgConnection, role: Role) -> Option<()> { - use diesel::pg::expression::dsl::any; - - let question_items: Vec = questions::table - .filter(any(questions::role_ids).eq(role.id)) - .load(conn) - .map_err(|x| { - eprintln!("error in delete_children: {x:?}"); - x - }) - .ok()?; - - diesel::delete(questions::table.filter(any(questions::role_ids).eq(role.id))) - .execute(conn) - .map_err(|x| { - eprintln!("error in delete_children: {x:?}"); - x - }) - .ok(); - - for question in question_items { - diesel::delete(answers::table.filter(answers::question_id.eq(question.id))) - .execute(conn) - .map_err(|x| { - eprintln!("error in delete_children: {x:?}"); - x - }) - .ok(); - } - - let application_items: Vec = applications::table - .filter(applications::role_id.eq(role.id)) - .load(conn) - .map_err(|x| { - eprintln!("error in delete_children: {x:?}"); - x - }) - .ok()?; - - for application in application_items { - Application::delete_deep(conn, application); - } - - diesel::delete(applications::table.filter(applications::role_id.eq(role.id))) - .execute(conn) - .map_err(|x| { - eprintln!("error in delete_children: {x:?}"); - x - }) - .ok(); - - Some(()) - } - - pub fn delete_deep(conn: &PgConnection, role_id: i32) -> Option<()> { - let questions = Question::get_all_from_role_id(conn, role_id); - let applications = Application::get_all_from_role_id(conn, role_id); - - questions.into_iter().for_each(|question| { - Question::delete_deep(conn, question.id); - }); - applications.into_iter().for_each(|application| { - Application::delete_deep(conn, application); - }); - - if Role::delete(conn, role_id) { - Some(()) - } else { - None - } - } -} - -impl RoleUpdate { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::roles::dsl::*; - - self.insert_into(roles).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize)] -#[belongs_to(Role)] -#[belongs_to(OrganisationUser, foreign_key = "user_id")] -pub struct Application { - pub id: i32, - pub user_id: i32, - pub role_id: i32, - pub status: ApplicationStatus, - pub private_status: ApplicationStatus, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable, FromForm, Deserialize)] -#[table_name = "applications"] -pub struct NewApplication { - pub user_id: i32, - pub role_id: i32, - pub status: ApplicationStatus, - pub private_status: ApplicationStatus, -} - -impl Application { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::applications::dsl::*; - - applications - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get(app_id: i32, conn: &PgConnection) -> Option { - use crate::database::schema::applications::dsl::*; - - applications.filter(id.eq(app_id)).first(conn).ok() - } - - pub fn get_all_from_user_id(conn: &PgConnection, user_id_val: i32) -> Vec { - use crate::database::schema::applications::dsl::*; - - applications - .filter(user_id.eq(user_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_role_id(conn: &PgConnection, role_id_val: i32) -> Vec { - use crate::database::schema::applications::dsl::*; - - applications - .filter(role_id.eq(role_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_campaign_id(conn: &PgConnection, role_id_val: i32) -> Vec { - use crate::database::schema::applications::dsl::*; - - applications - .filter(role_id.eq(role_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn delete(conn: &PgConnection, application_id: i32) -> bool { - use crate::database::schema::applications::dsl::*; - - diesel::delete(applications.filter(id.eq(application_id))) - .execute(conn) - .map_err(|x| { - eprintln!("error in delete application: {x:?}"); - x - }) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, application: Application) -> Option<()> { - let ratings = Rating::get_all_from_application_id(conn, application.id); - let comments = Comment::get_all_from_application_id(conn, application.id); - let answers = Answer::get_all_from_application_id(conn, application.id); - - ratings.into_iter().for_each(|rating| { - Rating::delete_deep(conn, rating.id); - }); - comments.into_iter().for_each(|comment| { - Comment::delete_deep(conn, comment.id); - }); - answers.into_iter().for_each(|answer| { - Answer::delete_deep(conn, answer.id); - }); - - match Application::delete(conn, application.id) { - true => Some(()), - false => None, - } - } -} - -impl NewApplication { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::applications::dsl::*; - - self.insert_into(applications).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, PartialEq, Serialize, Debug, QueryableByName)] -#[table_name = "questions"] -pub struct Question { - pub id: i32, - pub role_ids: Vec, - pub title: String, - pub description: Option, - pub max_bytes: i32, - pub required: bool, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable, Serialize, Deserialize)] -#[table_name = "questions"] -pub struct NewQuestion { - pub role_ids: Vec, - pub title: String, - pub description: Option, - #[serde(default)] - pub max_bytes: i32, - pub required: bool, -} - -#[derive(Serialize)] -pub struct QuestionResponse { - pub id: i32, - pub role_ids: Vec, - pub title: String, - pub description: Option, - pub max_bytes: i32, - pub required: bool, -} - -impl std::convert::From for QuestionResponse { - fn from(question: Question) -> Self { - Self { - id: question.id, - role_ids: question.role_ids, - title: question.title, - description: question.description, - max_bytes: question.max_bytes, - required: question.required, - } - } -} - -#[derive(FromForm, AsChangeset, Deserialize)] -#[table_name = "questions"] -pub struct UpdateQuestionInput { - pub title: String, - pub description: Option, - pub max_bytes: i32, - pub required: bool, -} - -impl Question { - pub fn get_first_role(&self) -> i32 { - *self - .role_ids - .get(0) - .expect("Question should be for at least one role") - } - - pub fn update( - conn: &PgConnection, - question_id: i32, - update_question: UpdateQuestionInput, - ) -> Option<()> { - use crate::database::schema::questions::dsl::*; - - diesel::update(questions.filter(id.eq(question_id))) - .set(update_question) - .execute(conn) - .ok()?; - - Some(()) - } - - pub fn get_all_from_role_id(conn: &PgConnection, role_id_val: i32) -> Vec { - diesel::sql_query(&format!( - "select * from questions where {} = any(role_ids)", - role_id_val - )) - .load::(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn delete_all_from_role_id(conn: &PgConnection, role_id_val: i32) -> bool { - diesel::sql_query(&format!( - "delete from questions where {} = any(role_ids)", - role_id_val - )) - .execute(conn) - .is_ok() - } - - pub fn delete(conn: &PgConnection, question_id: i32) -> bool { - use crate::database::schema::questions::dsl::*; - - diesel::delete(questions.filter(id.eq(question_id))) - .execute(conn) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, question_id: i32) -> bool { - Answer::get_all_from_question_id(conn, question_id) - .into_iter() - .for_each(|answer| { - Answer::delete_deep(conn, answer.id); - }); - - Question::delete(conn, question_id) - } - - pub fn get_from_id(conn: &PgConnection, question_id: i32) -> Option { - use crate::database::schema::questions::dsl::*; - - questions.filter(id.eq(question_id)).first(conn).ok() - } -} - -impl NewQuestion { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::questions::dsl::*; - - self.insert_into(questions).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize)] -#[belongs_to(Question)] -#[belongs_to(Application)] -pub struct Answer { - pub id: i32, - pub application_id: i32, - pub question_id: i32, - pub description: String, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable, Deserialize)] -#[table_name = "answers"] -pub struct NewAnswer { - pub application_id: i32, - pub question_id: i32, - pub description: String, -} - -impl Answer { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::answers::dsl::*; - - answers - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_application_id( - conn: &PgConnection, - application_id_val: i32, - ) -> Vec { - use crate::database::schema::answers::dsl::*; - - answers - .filter(application_id.eq(application_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_question_id(conn: &PgConnection, question_id_val: i32) -> Vec { - use crate::database::schema::answers::dsl::*; - - answers - .filter(question_id.eq(question_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn delete(conn: &PgConnection, answer_id_val: i32) -> bool { - use crate::database::schema::answers::dsl::*; - - diesel::delete(answers.filter(id.eq(answer_id_val))) - .execute(conn) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, answer_id_val: i32) -> bool { - use crate::database::schema::answers::dsl::*; - - diesel::delete(answers.filter(id.eq(answer_id_val))) - .execute(conn) - .is_ok() - } -} - -impl NewAnswer { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::answers::dsl::*; - - self.insert_into(answers).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize)] -#[belongs_to(Application)] -#[belongs_to(OrganisationUser, foreign_key = "commenter_user_id")] -pub struct Comment { - pub id: i32, - pub application_id: i32, - pub commenter_user_id: i32, - pub description: String, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable)] -#[table_name = "comments"] -pub struct NewComment { - pub application_id: i32, - pub commenter_user_id: i32, - pub description: String, -} - -impl Comment { - pub fn get_from_id(conn: &PgConnection, comment_id: i32) -> Option { - use crate::database::schema::comments::dsl::*; - - comments.filter(id.eq(comment_id)).first(conn).ok() - } - - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::comments::dsl::*; - - comments - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_application_id( - conn: &PgConnection, - application_id_val: i32, - ) -> Vec { - use crate::database::schema::comments::dsl::*; - - comments - .filter(application_id.eq(application_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn delete(conn: &PgConnection, comment_id_val: i32) -> bool { - use crate::database::schema::comments::dsl::*; - - diesel::delete(comments.filter(id.eq(comment_id_val))) - .execute(conn) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, comment_id_val: i32) -> bool { - use crate::database::schema::comments::dsl::*; - - diesel::delete(comments.filter(id.eq(comment_id_val))) - .execute(conn) - .is_ok() - } -} - -impl NewComment { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::comments::dsl::*; - - self.insert_into(comments).get_result(conn).ok() - } -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize)] -#[belongs_to(Application)] -#[belongs_to(OrganisationUser, foreign_key = "rater_user_id")] -pub struct Rating { - pub id: i32, - pub application_id: i32, - pub rater_user_id: i32, - pub rating: i32, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -#[derive(Insertable)] -#[table_name = "ratings"] -pub struct NewRating { - pub application_id: i32, - pub rater_user_id: i32, - pub rating: i32, -} - -impl Rating { - pub fn get_all(conn: &PgConnection) -> Vec { - use crate::database::schema::ratings::dsl::*; - - ratings - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_application_id( - conn: &PgConnection, - application_id_val: i32, - ) -> Vec { - use crate::database::schema::ratings::dsl::*; - - ratings - .filter(application_id.eq(application_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn get_all_from_rater_user_id(conn: &PgConnection, user_id_val: i32) -> Vec { - use crate::database::schema::ratings::dsl::*; - - ratings - .filter(rater_user_id.eq(user_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - } - - pub fn delete(conn: &PgConnection, rating_id_val: i32) -> bool { - use crate::database::schema::ratings::dsl::*; - diesel::delete(ratings.filter(id.eq(rating_id_val))) - .execute(conn) - .is_ok() - } - - pub fn delete_deep(conn: &PgConnection, rating_id_val: i32) -> bool { - use crate::database::schema::ratings::dsl::*; - diesel::delete(ratings.filter(id.eq(rating_id_val))) - .execute(conn) - .is_ok() - } -} - -impl NewRating { - pub fn insert(&self, conn: &PgConnection) -> Option { - use crate::database::schema::ratings::dsl::*; - - self.insert_into(ratings).get_result(conn).ok() - } -} - -#[derive(Serialize)] -pub struct GetQuestionsResponse { - pub questions: Vec, -} - -#[derive(Serialize)] -pub struct CampaignInfo { - pub id: i32, - pub name: String, - pub cover_image: Option, - pub starts_at: NaiveDateTime, - pub ends_at: NaiveDateTime, -} - -impl std::convert::From for CampaignInfo { - fn from(campaign: Campaign) -> Self { - Self { - id: campaign.id, - name: campaign.name, - cover_image: campaign - .cover_image - .map(|image| get_http_image_path(ImageLocation::CAMPAIGNS, &image)), - starts_at: campaign.starts_at, - ends_at: campaign.ends_at, - } - } -} - -#[derive(Serialize)] -pub struct OrganisationUserInfo { - pub id: i32, - pub display_name: String, - pub role: AdminLevel, -} - -impl OrganisationUserInfo { - pub fn get_all_from_organisation_id( - conn: &PgConnection, - organisation_id_val: i32, - ) -> Vec { - use crate::database::schema::organisation_users::dsl::*; - - organisation_users - .filter(organisation_id.eq(organisation_id_val)) - .order(id.asc()) - .load(conn) - .unwrap_or_else(|_| vec![]) - .into_iter() - .map(|o: OrganisationUser| { - let user = User::get_from_id(conn, o.user_id).unwrap(); - Self { - id: o.user_id, - display_name: user.display_name, - role: o.admin_level, - } - }) - .collect() - } -} - -#[derive(Serialize)] -pub struct OrganisationInfo { - pub id: i32, - pub name: String, - pub logo: Option, - pub members: Vec, - pub campaigns: Vec, -} - -impl OrganisationInfo { - pub fn new(organisation_id: i32, conn: &PgConnection) -> Self { - let organisation = Organisation::get_from_id(conn, organisation_id).unwrap(); - Self { - id: organisation.id, - name: organisation.name, - logo: organisation - .logo - .map(|logo_uuid| get_http_image_path(ImageLocation::ORGANISATIONS, &logo_uuid)), - members: OrganisationUserInfo::get_all_from_organisation_id(conn, organisation.id), - campaigns: Campaign::get_all_from_org_id(conn, organisation.id) - .into_iter() - .map(|c: Campaign| CampaignInfo::from(c)) - .collect(), - } - } -} - -#[derive(Serialize)] -pub struct AdminInfoResponse { - pub organisations: Vec, -} diff --git a/backend/server/src/database/schema.rs b/backend/server/src/database/schema.rs deleted file mode 100644 index a5a526d4..00000000 --- a/backend/server/src/database/schema.rs +++ /dev/null @@ -1,197 +0,0 @@ -use diesel_derive_enum::DbEnum; -use rocket::FromFormField; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, DbEnum, PartialEq, FromFormField, Serialize, Deserialize, Clone, Copy)] -#[DbValueStyle = "PascalCase"] -pub enum ApplicationStatus { - Draft, - Pending, - Rejected, - Success, -} - -#[derive(Debug, DbEnum, PartialEq, Serialize, Deserialize, Clone, Copy)] -#[DbValueStyle = "PascalCase"] -pub enum AdminLevel { - ReadOnly = 1, - Director, - Admin, -} - -impl AdminLevel { - pub fn geq(self, other: Self) -> bool { - self as i32 >= other as i32 - } -} - -#[derive(Debug, DbEnum, PartialEq, Serialize, Deserialize, Clone, Copy)] -#[DbValueStyle = "PascalCase"] -pub enum UserGender { - Female, - Male, - Unspecified, -} - -table! { - answers (id) { - id -> Int4, - application_id -> Int4, - question_id -> Int4, - description -> Text, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - use diesel::sql_types::*; - use super::ApplicationStatusMapping; - - applications (id) { - id -> Int4, - user_id -> Int4, - role_id -> Int4, - status -> ApplicationStatusMapping, - private_status -> ApplicationStatusMapping, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - campaigns (id) { - id -> Int4, - organisation_id -> Int4, - name -> Text, - cover_image -> Nullable, - description -> Text, - starts_at -> Timestamp, - ends_at -> Timestamp, - published -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - comments (id) { - id -> Int4, - application_id -> Int4, - commenter_user_id -> Int4, - description -> Text, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - use diesel::sql_types::*; - use super::AdminLevelMapping; - - organisation_users (id) { - id -> Int4, - user_id -> Int4, - organisation_id -> Int4, - admin_level -> AdminLevelMapping, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - organisations (id) { - id -> Int4, - name -> Text, - logo -> Nullable, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - use diesel::sql_types::*; - - questions (id) { - id -> Int4, - role_ids -> Array, - title -> Text, - description -> Nullable, - max_bytes -> Int4, - required -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - ratings (id) { - id -> Int4, - application_id -> Int4, - rater_user_id -> Int4, - rating -> Int4, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - roles (id) { - id -> Int4, - campaign_id -> Int4, - name -> Text, - description -> Nullable, - min_available -> Int4, - max_available -> Int4, - finalised -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -table! { - use diesel::sql_types::*; - use super::UserGenderMapping; - - users (id) { - id -> Int4, - email -> Text, - zid -> Text, - display_name -> Text, - degree_name -> Text, - degree_starting_year -> Int4, - gender -> UserGenderMapping, - pronouns -> Text, - superuser -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -joinable!(answers -> applications (application_id)); -joinable!(answers -> questions (question_id)); -joinable!(applications -> roles (role_id)); -joinable!(applications -> users (user_id)); -joinable!(campaigns -> organisations (organisation_id)); -joinable!(comments -> applications (application_id)); -joinable!(comments -> users (commenter_user_id)); -joinable!(organisation_users -> organisations (organisation_id)); -joinable!(organisation_users -> users (user_id)); -// TODO: can probably make this work to auto join but idk how -// joinable!(questions -> roles (role_id)); -joinable!(ratings -> applications (application_id)); -joinable!(ratings -> users (rater_user_id)); -joinable!(roles -> campaigns (campaign_id)); - -allow_tables_to_appear_in_same_query!( - answers, - applications, - campaigns, - comments, - organisation_users, - organisations, - questions, - ratings, - roles, - users, -); diff --git a/backend/server/src/diesel.toml b/backend/server/src/diesel.toml deleted file mode 100644 index bfb01bcc..00000000 --- a/backend/server/src/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/database/schema.rs" diff --git a/backend/server/src/error.rs b/backend/server/src/error.rs deleted file mode 100644 index b73a1cb1..00000000 --- a/backend/server/src/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use rocket::http::Status; -use rocket::request::Request; -use rocket::response::{self, Responder}; -use rocket::serde::{json::Json, Serialize}; - -pub struct JsonErr(pub T, pub Status); - -impl<'r, T: Serialize> Responder<'r, 'r> for JsonErr { - fn respond_to(self, r: &Request) -> response::Result<'r> { - let json_self = Json(self.0); - json_self.respond_to(r).map(|mut r| { - r.set_status(self.1); - r - }) - } -} diff --git a/backend/server/src/guard/mod.rs b/backend/server/src/guard/mod.rs deleted file mode 100644 index e97ade67..00000000 --- a/backend/server/src/guard/mod.rs +++ /dev/null @@ -1,88 +0,0 @@ -use rocket::{ - http::Status, - request::{self, FromRequest, Outcome}, - Request, -}; - -use crate::{ - auth::{Auth, AuthError}, - database::{ - models::{SuperUser, User}, - Database, - }, -}; - -#[derive(Debug)] -pub enum UserError { - AuthError(AuthError), - AccountNoLongerExists, -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for User { - type Error = UserError; - - async fn from_request(request: &'r Request<'_>) -> request::Outcome { - let auth = match request.guard::().await { - Outcome::Success(auth) => auth, - Outcome::Failure((status, error)) => { - return Outcome::Failure((status, UserError::AuthError(error))) - } - Outcome::Forward(forward) => return Outcome::Forward(forward), - }; - - let database = request.guard::().await.unwrap(); - - let user_id = auth.jwt.user_id; - let user = database - .run(move |conn| User::get_from_id(conn, user_id as i32)) - .await; - - match user { - Some(user) => Outcome::Success(user), - None => Outcome::Failure(( - Status::InternalServerError, - UserError::AccountNoLongerExists, - )), - } - } -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for SuperUser { - type Error = UserError; - - async fn from_request(request: &'r Request<'_>) -> request::Outcome { - let auth = match request.guard::().await { - Outcome::Success(auth) => auth, - Outcome::Failure((status, error)) => { - return Outcome::Failure((status, UserError::AuthError(error))) - } - Outcome::Forward(forward) => return Outcome::Forward(forward), - }; - - let database = request.guard::().await.unwrap(); - - let user_id = auth.jwt.user_id; - let user = database - .run(move |conn| User::get_from_id(conn, user_id as i32)) - .await; - - match user { - Some(user) => { - if user.superuser { - Outcome::Success(SuperUser::new(user)) - } else { - Outcome::Failure(( - Status::Forbidden, - UserError::AuthError(AuthError::NotSuperUser), - )) - } - } - None => Outcome::Failure(( - Status::InternalServerError, - UserError::AccountNoLongerExists, - )), - } - } -} diff --git a/backend/server/src/handler/application.rs b/backend/server/src/handler/application.rs new file mode 100644 index 00000000..15d75e41 --- /dev/null +++ b/backend/server/src/handler/application.rs @@ -0,0 +1,51 @@ +use crate::models::app::AppState; +use crate::models::application::{Application, ApplicationStatus}; +use crate::models::auth::{AuthUser, ApplicationAdmin}; +use crate::models::error::ChaosError; +use crate::models::transaction::DBTransaction; +use axum::extract::{Json, Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; + +pub struct ApplicationHandler; + +impl ApplicationHandler { + pub async fn get( + Path(application_id): Path, + _admin: ApplicationAdmin, + mut transaction: DBTransaction<'_>, + ) -> Result { + let application = Application::get(application_id, &mut transaction.tx).await?; + transaction.tx.commit().await?; + Ok((StatusCode::OK, Json(application))) + } + + pub async fn set_status( + State(state): State, + Path(application_id): Path, + _admin: ApplicationAdmin, + Json(data): Json, + ) -> Result { + Application::set_status(application_id, data, &state.db).await?; + Ok((StatusCode::OK, "Status successfully updated")) + } + + pub async fn set_private_status( + State(state): State, + Path(application_id): Path, + _admin: ApplicationAdmin, + Json(data): Json, + ) -> Result { + Application::set_private_status(application_id, data, &state.db).await?; + Ok((StatusCode::OK, "Private Status successfully updated")) + } + + pub async fn get_from_curr_user( + user: AuthUser, + mut transaction: DBTransaction<'_>, + ) -> Result { + let applications = Application::get_from_user_id(user.user_id, &mut transaction.tx).await?; + transaction.tx.commit().await?; + Ok((StatusCode::OK, Json(applications))) + } +} \ No newline at end of file diff --git a/backend/server/src/handler/auth.rs b/backend/server/src/handler/auth.rs new file mode 100644 index 00000000..12fcab02 --- /dev/null +++ b/backend/server/src/handler/auth.rs @@ -0,0 +1,52 @@ +use crate::models::app::AppState; +use crate::models::auth::{AuthRequest, GoogleUserProfile}; +use crate::models::error::ChaosError; +use crate::service::auth::create_or_get_user_id; +use crate::service::jwt::encode_auth_token; +use axum::extract::{Query, State}; +use axum::response::IntoResponse; +use axum::Extension; +use oauth2::basic::BasicClient; +use oauth2::reqwest::async_http_client; +use oauth2::{AuthorizationCode, TokenResponse}; + +/// This function handles the passing in of the Google OAuth code. After allowing our app the +/// requested permissions, the user is redirected to this url on our server, where we use the +/// code to get the user's email address from Google's OpenID Connect API. +pub async fn google_callback( + State(state): State, + Query(query): Query, + Extension(oauth_client): Extension, +) -> Result { + let token = oauth_client + .exchange_code(AuthorizationCode::new(query.code)) + .request_async(async_http_client) + .await?; + + let profile = state + .ctx + .get("https://openidconnect.googleapis.com/v1/userinfo") + .bearer_auth(token.access_token().secret().to_owned()) + .send() + .await?; + + let profile = profile.json::().await.unwrap(); + + let user_id = create_or_get_user_id( + profile.email.clone(), + profile.name, + state.db, + state.snowflake_generator, + ) + .await + .unwrap(); + + // TODO: Return JWT as set-cookie header. + let token = encode_auth_token( + profile.email, + user_id, + &state.encoding_key, + &state.jwt_header, + ); + Ok(token) +} diff --git a/backend/server/src/handler/campaign.rs b/backend/server/src/handler/campaign.rs new file mode 100644 index 00000000..f5a827d3 --- /dev/null +++ b/backend/server/src/handler/campaign.rs @@ -0,0 +1,103 @@ +use crate::models; +use crate::models::app::AppState; +use crate::models::application::Application; +use crate::models::application::NewApplication; +use crate::models::auth::AuthUser; +use crate::models::auth::CampaignAdmin; +use crate::models::campaign::Campaign; +use crate::models::error::ChaosError; +use crate::models::role::{Role, RoleUpdate}; +use crate::models::transaction::DBTransaction; +use axum::extract::{Json, Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; + +pub struct CampaignHandler; +impl CampaignHandler { + pub async fn get( + State(state): State, + Path(id): Path, + _user: AuthUser, + ) -> Result { + let campaign = Campaign::get(id, &state.db).await?; + Ok((StatusCode::OK, Json(campaign))) + } + + pub async fn get_all( + State(state): State, + _user: AuthUser, + ) -> Result { + let campaigns = Campaign::get_all(&state.db).await?; + Ok((StatusCode::OK, Json(campaigns))) + } + + pub async fn update( + State(state): State, + Path(id): Path, + _admin: CampaignAdmin, + Json(request_body): Json, + ) -> Result { + Campaign::update(id, request_body, &state.db).await?; + Ok((StatusCode::OK, "Successfully updated campaign")) + } + + pub async fn update_banner( + State(state): State, + Path(id): Path, + _admin: CampaignAdmin, + ) -> Result { + let banner_url = Campaign::update_banner(id, &state.db, &state.storage_bucket).await?; + Ok((StatusCode::OK, Json(banner_url))) + } + + pub async fn delete( + State(state): State, + Path(id): Path, + _admin: CampaignAdmin, + ) -> Result { + Campaign::delete(id, &state.db).await?; + Ok((StatusCode::OK, "Successfully deleted campaign")) + } + + pub async fn create_role( + State(state): State, + Path(id): Path, + _admin: CampaignAdmin, + Json(data): Json, + ) -> Result { + Role::create(id, data, &state.db, state.snowflake_generator).await?; + Ok((StatusCode::OK, "Successfully created role")) + } + + pub async fn get_roles( + State(state): State, + Path(id): Path, + _user: AuthUser, + ) -> Result { + let roles = Role::get_all_in_campaign(id, &state.db).await?; + + Ok((StatusCode::OK, Json(roles))) + } + + pub async fn create_application( + State(state): State, + Path(id): Path, + user: AuthUser, + mut transaction: DBTransaction<'_>, + Json(data): Json, + ) -> Result { + Application::create(id, user.user_id, data, state.snowflake_generator, &mut transaction.tx).await?; + transaction.tx.commit().await?; + Ok((StatusCode::OK, "Successfully created application")) + } + + pub async fn get_applications( + Path(id): Path, + _admin: CampaignAdmin, + mut transaction: DBTransaction<'_>, + ) -> Result { + let applications = Application::get_from_campaign_id(id, &mut transaction.tx).await?; + transaction.tx.commit().await?; + Ok((StatusCode::OK, Json(applications))) + } +} diff --git a/backend/server/src/handler/mod.rs b/backend/server/src/handler/mod.rs new file mode 100644 index 00000000..e0a5a8f6 --- /dev/null +++ b/backend/server/src/handler/mod.rs @@ -0,0 +1,6 @@ +pub mod auth; +pub mod user; +pub mod campaign; +pub mod organisation; +pub mod role; +pub mod application; diff --git a/backend/server/src/handler/organisation.rs b/backend/server/src/handler/organisation.rs new file mode 100644 index 00000000..78ec8bc6 --- /dev/null +++ b/backend/server/src/handler/organisation.rs @@ -0,0 +1,159 @@ +use crate::models; +use crate::models::app::AppState; +use crate::models::auth::SuperUser; +use crate::models::auth::{AuthUser, OrganisationAdmin}; +use crate::models::error::ChaosError; +use crate::models::organisation::{AdminToRemove, AdminUpdateList, NewOrganisation, Organisation}; +use crate::models::transaction::DBTransaction; +use axum::extract::{Json, Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; + +pub struct OrganisationHandler; + +impl OrganisationHandler { + pub async fn create( + State(state): State, + _user: SuperUser, + mut transaction: DBTransaction<'_>, + Json(data): Json, + ) -> Result { + Organisation::create( + data.admin, + data.name, + state.snowflake_generator, + &mut transaction.tx, + ) + .await?; + + transaction.tx.commit().await?; + Ok((StatusCode::OK, "Successfully created organisation")) + } + + pub async fn get( + State(state): State, + Path(id): Path, + _user: AuthUser, + ) -> Result { + let org = Organisation::get(id, &state.db).await?; + Ok((StatusCode::OK, Json(org))) + } + + pub async fn delete( + State(state): State, + Path(id): Path, + _user: SuperUser, + ) -> Result { + Organisation::delete(id, &state.db).await?; + Ok((StatusCode::OK, "Successfully deleted organisation")) + } + + pub async fn get_admins( + State(state): State, + Path(id): Path, + _user: SuperUser, + ) -> Result { + let members = Organisation::get_admins(id, &state.db).await?; + Ok((StatusCode::OK, Json(members))) + } + + pub async fn get_members( + State(state): State, + Path(id): Path, + _admin: OrganisationAdmin, + ) -> Result { + let members = Organisation::get_members(id, &state.db).await?; + Ok((StatusCode::OK, Json(members))) + } + + pub async fn update_admins( + Path(id): Path, + _super_user: SuperUser, + mut transaction: DBTransaction<'_>, + Json(request_body): Json, + ) -> Result { + Organisation::update_admins(id, request_body.members, &mut transaction.tx).await?; + + transaction.tx.commit().await?; + Ok((StatusCode::OK, "Successfully updated organisation members")) + } + + pub async fn update_members( + mut transaction: DBTransaction<'_>, + Path(id): Path, + _admin: OrganisationAdmin, + Json(request_body): Json, + ) -> Result { + Organisation::update_members(id, request_body.members, &mut transaction.tx).await?; + + transaction.tx.commit().await?; + Ok((StatusCode::OK, "Successfully updated organisation members")) + } + + pub async fn remove_admin( + State(state): State, + Path(id): Path, + _super_user: SuperUser, + Json(request_body): Json, + ) -> Result { + Organisation::remove_admin(id, request_body.user_id, &state.db).await?; + + Ok(( + StatusCode::OK, + "Successfully removed member from organisation", + )) + } + + pub async fn remove_member( + State(state): State, + Path(id): Path, + _admin: OrganisationAdmin, + Json(request_body): Json, + ) -> Result { + Organisation::remove_member(id, request_body.user_id, &state.db).await?; + + Ok(( + StatusCode::OK, + "Successfully removed member from organisation", + )) + } + + pub async fn update_logo( + State(state): State, + Path(id): Path, + _admin: OrganisationAdmin, + ) -> Result { + let logo_url = Organisation::update_logo(id, &state.db, &state.storage_bucket).await?; + Ok((StatusCode::OK, Json(logo_url))) + } + + pub async fn get_campaigns( + State(state): State, + Path(id): Path, + _user: AuthUser, + ) -> Result { + let campaigns = Organisation::get_campaigns(id, &state.db).await?; + + Ok((StatusCode::OK, Json(campaigns))) + } + + pub async fn create_campaign( + Path(id): Path, + State(mut state): State, + _admin: OrganisationAdmin, + Json(request_body): Json, + ) -> Result { + Organisation::create_campaign( + id, + request_body.name, + request_body.description, + request_body.starts_at, + request_body.ends_at, + &state.db, + &mut state.snowflake_generator, + ) + .await?; + + Ok((StatusCode::OK, "Successfully created campaign")) + } +} diff --git a/backend/server/src/handler/role.rs b/backend/server/src/handler/role.rs new file mode 100644 index 00000000..c1e9d2da --- /dev/null +++ b/backend/server/src/handler/role.rs @@ -0,0 +1,51 @@ +use crate::models::app::AppState; +use crate::models::application::Application; +use crate::models::auth::{AuthUser, RoleAdmin}; +use crate::models::error::ChaosError; +use crate::models::role::{Role, RoleUpdate}; +use axum::extract::{Json, Path, State}; +use crate::models::transaction::DBTransaction; +use axum::http::StatusCode; +use axum::response::IntoResponse; + +pub struct RoleHandler; + +impl RoleHandler { + pub async fn get( + State(state): State, + Path(id): Path, + _user: AuthUser, + ) -> Result { + let role = Role::get(id, &state.db).await?; + Ok((StatusCode::OK, Json(role))) + } + + pub async fn delete( + State(state): State, + Path(id): Path, + _admin: RoleAdmin, + ) -> Result { + Role::delete(id, &state.db).await?; + Ok((StatusCode::OK, "Successfully deleted role")) + } + + pub async fn update( + State(state): State, + Path(id): Path, + _admin: RoleAdmin, + Json(data): Json, + ) -> Result { + Role::update(id, data, &state.db).await?; + Ok((StatusCode::OK, "Successfully updated role")) + } + + pub async fn get_applications( + Path(id): Path, + _admin: RoleAdmin, + mut transaction: DBTransaction<'_>, + ) -> Result { + let applications = Application::get_from_role_id(id, &mut transaction.tx).await?; + transaction.tx.commit().await?; + Ok((StatusCode::OK, Json(applications))) + } +} diff --git a/backend/server/src/handler/user.rs b/backend/server/src/handler/user.rs new file mode 100644 index 00000000..4909ba91 --- /dev/null +++ b/backend/server/src/handler/user.rs @@ -0,0 +1,77 @@ +use crate::models::app::AppState; +use crate::models::auth::AuthUser; +use crate::models::error::ChaosError; +use axum::extract::{Json, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use crate::models::user::{User, UserDegree, UserGender, UserName, UserPronouns, UserZid}; + +pub struct UserHandler; + +impl UserHandler { + pub async fn get( + State(state): State, + user: AuthUser, + ) -> Result { + let user = User::get(user.user_id, &state.db).await?; + Ok((StatusCode::OK, Json(user))) + } + + pub async fn update_name( + State(state): State, + user: AuthUser, + Json(request_body): Json, + ) -> Result { + User::update_name(user.user_id, request_body.name, &state.db).await?; + + Ok((StatusCode::OK, "Updated username")) + } + + pub async fn update_pronouns( + State(state): State, + user: AuthUser, + Json(request_body): Json, + ) -> Result { + User::update_pronouns(user.user_id, request_body.pronouns, &state.db).await?; + + Ok((StatusCode::OK, "Updated pronouns")) + } + + pub async fn update_gender( + State(state): State, + user: AuthUser, + Json(request_body): Json, + ) -> Result { + User::update_gender(user.user_id, request_body.gender, &state.db).await?; + + Ok((StatusCode::OK, "Updated gender")) + } + + pub async fn update_zid( + State(state): State, + user: AuthUser, + Json(request_body): Json, + ) -> Result { + User::update_zid(user.user_id, request_body.zid, &state.db).await?; + + Ok((StatusCode::OK, "Updated zid")) + } + + pub async fn update_degree( + State(state): State, + user: AuthUser, + Json(request_body): Json, + ) -> Result { + User::update_degree( + user.user_id, + request_body.degree_name, + request_body.degree_starting_year, + &state.db + ) + .await?; + + Ok((StatusCode::OK, "Updated user degree")) + } +} + + diff --git a/backend/server/src/images.rs b/backend/server/src/images.rs deleted file mode 100644 index c7255beb..00000000 --- a/backend/server/src/images.rs +++ /dev/null @@ -1,86 +0,0 @@ -use image::{io::Reader as ImageReader, DynamicImage, ImageError}; -use rocket::{data::ToByteUnit, Data}; -use std::{ - fs, - io::{self, Cursor}, - path::{Path, PathBuf}, -}; -use strum::{EnumIter, IntoEnumIterator}; -use webp::Encoder; - -pub enum ImageDecodeError { - ImageError(ImageError), - IoError(io::Error), -} - -impl From for ImageDecodeError { - fn from(error: ImageError) -> Self { - ImageDecodeError::ImageError(error) - } -} - -impl From for ImageDecodeError { - fn from(error: io::Error) -> Self { - ImageDecodeError::IoError(error) - } -} - -pub fn try_decode_bytes(bytes: Vec) -> Result { - ImageReader::new(Cursor::new(bytes)) - .with_guessed_format()? - .decode() -} - -#[rustfmt::skip] -pub async fn try_decode_data(data: Data<'_>) -> Result { - data.open(5.mebibytes()).into_bytes().await.map(|bytes| try_decode_bytes(bytes.to_vec())).map_err(|e| e.into()).and_then(|v| v.map_err(|e| e.into())) -} - -const HTTP_IMAGE_BASE_PATH: &str = "/api/static/images/"; -pub const IMAGE_BASE_PATH: &str = "./images/"; - -#[derive(EnumIter)] -pub enum ImageLocation { - CAMPAIGNS, - ORGANISATIONS, -} - -pub fn image_location_to_string(location: ImageLocation) -> String { - match location { - ImageLocation::CAMPAIGNS => "campaigns/", - ImageLocation::ORGANISATIONS => "organisations/", - } - .to_string() -} - -pub fn save_image( - image: DynamicImage, - location: ImageLocation, - image_uuid: &str, -) -> std::io::Result { - ImageLocation::iter().for_each(|location| { - fs::create_dir_all(Path::new(IMAGE_BASE_PATH).join(image_location_to_string(location))) - .ok(); - }); - - let path = get_image_path(location, image_uuid); - - let encoder = Encoder::from_image(&image).unwrap(); - let webp = encoder.encode(80.0); - std::fs::write(&path, &*webp).map(|_| path) -} - -pub fn get_image_path(location: ImageLocation, image_uuid: &str) -> PathBuf { - Path::new(IMAGE_BASE_PATH) - .join(image_location_to_string(location)) - .join(image_uuid) -} - -pub fn get_http_image_path(location: ImageLocation, image_uuid: &str) -> String { - Path::new(HTTP_IMAGE_BASE_PATH) - .join(image_location_to_string(location)) - .join(image_uuid) - .to_str() - .unwrap() - .to_string() -} diff --git a/backend/server/src/lib.rs b/backend/server/src/lib.rs deleted file mode 100644 index 80b721c5..00000000 --- a/backend/server/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -extern crate diesel; - -pub mod admin; -pub mod application; -pub mod auth; -pub mod campaigns; -pub mod comment; -pub mod cors; -pub mod database; -pub mod error; -pub mod guard; -pub mod images; -pub mod organisation; -pub mod permissions; -pub mod question; -pub mod role; -pub mod state; -pub mod static_resources; -pub mod user; diff --git a/backend/server/src/main.rs b/backend/server/src/main.rs new file mode 100644 index 00000000..a0a7eaac --- /dev/null +++ b/backend/server/src/main.rs @@ -0,0 +1,155 @@ +use crate::handler::auth::google_callback; +use handler::user::UserHandler; +use crate::handler::campaign::CampaignHandler; +use crate::handler::organisation::OrganisationHandler; +use crate::handler::application::ApplicationHandler; +use crate::models::storage::Storage; +use anyhow::Result; +use axum::routing::{get, patch, post}; +use axum::Router; +use handler::role::RoleHandler; +use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation}; +use models::app::AppState; +use snowflake::SnowflakeIdGenerator; +use sqlx::postgres::PgPoolOptions; +use std::env; + +mod handler; +mod models; +mod service; + +#[tokio::main] +async fn main() -> Result<()> { + dotenvy::dotenv()?; + + // Initialise DB connection + let db_url = env::var("DATABASE_URL") + .expect("Error getting DATABASE_URL") + .to_string(); + let pool = PgPoolOptions::new() + .max_connections(5) + .connect(db_url.as_str()) + .await + .expect("Cannot connect to database"); + + // Initialise JWT settings + let jwt_secret = env::var("JWT_SECRET") + .expect("Error getting JWT_SECRET") + .to_string(); + // let jwt_secret = "I want to cry"; + let encoding_key = EncodingKey::from_secret(jwt_secret.as_bytes()); + let decoding_key = DecodingKey::from_secret(jwt_secret.as_bytes()); + let jwt_header = Header::new(Algorithm::HS512); + let mut jwt_validator = Validation::new(Algorithm::HS512); + jwt_validator.set_issuer(&["Chaos"]); + jwt_validator.set_audience(&["chaos.devsoc.app"]); + + // Initialise reqwest client + let ctx = reqwest::Client::new(); + + // Initialise Snowflake Generator + let snowflake_generator = SnowflakeIdGenerator::new(1, 1); + + // Initialise S3 bucket + let storage_bucket = Storage::init_bucket(); + + // Add all data to AppState + let state = AppState { + db: pool, + ctx, + encoding_key, + decoding_key, + jwt_header, + jwt_validator, + snowflake_generator, + storage_bucket, + }; + + let app = Router::new() + .route("/", get(|| async { "Hello, World!" })) + .route("/api/auth/callback/google", get(google_callback)) + .route("/api/v1/user", get(UserHandler::get)) + .route("/api/v1/user/name", patch(UserHandler::update_name)) + .route("/api/v1/user/pronouns", patch(UserHandler::update_pronouns)) + .route("/api/v1/user/gender", patch(UserHandler::update_gender)) + .route("/api/v1/user/zid", patch(UserHandler::update_zid)) + .route("/api/v1/user/degree", patch(UserHandler::update_degree)) + .route( + "/api/v1/user/applications", + get(ApplicationHandler::get_from_curr_user), + ) + .route("/api/v1/organisation", post(OrganisationHandler::create)) + .route( + "/api/v1/organisation/:organisation_id", + get(OrganisationHandler::get).delete(OrganisationHandler::delete), + ) + .route( + "/api/v1/organisation/:organisation_id/campaign", + post(OrganisationHandler::create_campaign), + ) + .route( + "/api/v1/organisation/:organisation_id/campaigns", + get(OrganisationHandler::get_campaigns), + ) + .route( + "/api/v1/organisation/:organisation_id/logo", + patch(OrganisationHandler::update_logo), + ) + .route( + "/api/v1/organisation/:organisation_id/member", + get(OrganisationHandler::get_members) + .put(OrganisationHandler::update_members) + .delete(OrganisationHandler::remove_member), + ) + .route( + "/api/v1/organisation/:organisation_id/admin", + get(OrganisationHandler::get_admins) + .put(OrganisationHandler::update_admins) + .delete(OrganisationHandler::remove_admin), + ) + .route( + "/api/v1/campaign/:campaign_id/role", + post(CampaignHandler::create_role), + ) + .route( + "/api/v1/campaign/:campaign_id/roles", + get(CampaignHandler::get_roles), + ) + .route( + "/api/v1/campaign/:campaign_id/applications", + get(CampaignHandler::get_applications), + ) + .route( + "/api/v1/role/:role_id", + get(RoleHandler::get) + .put(RoleHandler::update) + .delete(RoleHandler::delete), + ) + .route( + "/api/v1/role/:role_id/applications", + get(RoleHandler::get_applications) + ) + .route( + "/api/v1/campaign/:campaign_id", + get(CampaignHandler::get) + .put(CampaignHandler::update) + .delete(CampaignHandler::delete), + ) + .route("/api/v1/campaign", get(CampaignHandler::get_all)) + .route( + "/api/v1/campaign/:campaign_id/banner", + patch(CampaignHandler::update_banner), + ) + .route("api/v1/campaign/:campaign_id/application", + post(CampaignHandler::create_application) + ) + .route("api/v1/application/:application_id", get(ApplicationHandler::get)) + .route("api/v1/application/:application_id/status", patch(ApplicationHandler::set_status)) + .route("api/v1/application/:application_id/private", patch(ApplicationHandler::set_private_status)) + .with_state(state); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + axum::serve(listener, app).await.unwrap(); + + Ok(()) +} diff --git a/backend/server/src/models/app.rs b/backend/server/src/models/app.rs new file mode 100644 index 00000000..19ecfbff --- /dev/null +++ b/backend/server/src/models/app.rs @@ -0,0 +1,17 @@ +use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation}; +use reqwest::Client as ReqwestClient; +use s3::Bucket; +use snowflake::SnowflakeIdGenerator; +use sqlx::{Pool, Postgres}; + +#[derive(Clone)] +pub struct AppState { + pub db: Pool, + pub ctx: ReqwestClient, + pub decoding_key: DecodingKey, + pub encoding_key: EncodingKey, + pub jwt_header: Header, + pub jwt_validator: Validation, + pub snowflake_generator: SnowflakeIdGenerator, + pub storage_bucket: Bucket, +} diff --git a/backend/server/src/models/application.rs b/backend/server/src/models/application.rs new file mode 100644 index 00000000..65e55d21 --- /dev/null +++ b/backend/server/src/models/application.rs @@ -0,0 +1,389 @@ +use crate::models::error::ChaosError; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use snowflake::SnowflakeIdGenerator; +use sqlx::{FromRow, Pool, Postgres, Transaction}; +use std::ops::DerefMut; +use crate::models::user::UserDetails; + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct Application { + pub id: i64, + pub campaign_id: i64, + pub user_id: i64, + pub status: ApplicationStatus, + pub private_status: ApplicationStatus, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/* + User could apply for more than one roles at a time, for each application + into a role it will be represented by row in application_roles table which + is linked to the main Application body through application_id +*/ +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct ApplicationRole { + pub id: i64, + pub application_id: i64, + pub campaign_role_id: i64, +} + +#[derive(Deserialize, Serialize)] +pub struct NewApplication { + pub applied_roles: Vec, +} + +#[derive(Deserialize, Serialize)] +pub struct ApplicationDetails { + pub id: i64, + pub campaign_id: i64, + pub user: UserDetails, + pub status: ApplicationStatus, + pub private_status: ApplicationStatus, + pub applied_roles: Vec +} + +#[derive(Deserialize, Serialize)] +pub struct ApplicationData { + pub id: i64, + pub campaign_id: i64, + pub user_id: i64, + pub user_email: String, + pub user_zid: Option, + pub user_name: String, + pub user_pronouns: String, + pub user_gender: String, + pub user_degree_name: Option, + pub user_degree_starting_year: Option, + pub status: ApplicationStatus, + pub private_status: ApplicationStatus, +} + +#[derive(Deserialize, Serialize)] +pub struct ApplicationAppliedRoleDetails { + pub campaign_role_id: i64, + pub role_name: String, +} + + +#[derive(Deserialize, Serialize, sqlx::Type, Clone, Debug)] +#[sqlx(type_name = "application_status", rename_all = "PascalCase")] +pub enum ApplicationStatus { + Pending, + Rejected, + Successful, +} + +impl Application { + pub async fn create( + campaign_id: i64, + user_id: i64, + application_data: NewApplication, + mut snowflake_generator: SnowflakeIdGenerator, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { + let id = snowflake_generator.generate(); + + // Insert into table applications + sqlx::query!( + " + INSERT INTO applications (id, campaign_id, user_id) + VALUES ($1, $2, $3) + ", + id, + campaign_id, + user_id + ) + .execute(transaction.deref_mut()) + .await?; + + // Insert into table application_roles + for role_applied in application_data.applied_roles { + sqlx::query!( + " + INSERT INTO application_roles (application_id, campaign_role_id) + VALUES ($1, $2) + ", + id, + role_applied.campaign_role_id + ) + .execute(transaction.deref_mut()) + .await?; + } + + Ok(()) + } + + /* + Get Application given an application id + */ + pub async fn get(id: i64, transaction: &mut Transaction<'_, Postgres>,) -> Result { + let application_data = sqlx::query_as!( + ApplicationData, + " + SELECT a.id AS id, campaign_id, user_id, status AS \"status: ApplicationStatus\", + private_status AS \"private_status: ApplicationStatus\", u.email AS user_email, + u.zid AS user_zid, u.name AS user_name, u.gender AS user_gender, + u.pronouns AS user_pronouns, u.degree_name AS user_degree_name, + u.degree_starting_year AS user_degree_starting_year + FROM applications a LEFT JOIN users u ON u.id = a.user_id + WHERE a.id = $1 + ", + id + ) + .fetch_one(transaction.deref_mut()) + .await?; + + let applied_roles = sqlx::query_as!( + ApplicationAppliedRoleDetails, + " + SELECT application_roles.campaign_role_id, campaign_roles.name AS role_name + FROM application_roles + LEFT JOIN campaign_roles + ON application_roles.campaign_role_id = campaign_roles.id + WHERE application_id = $1 + ", + id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + Ok( + ApplicationDetails { + id: application_data.id, + campaign_id: application_data.campaign_id, + status: application_data.status, + private_status: application_data.private_status, + applied_roles, + user: UserDetails { + id: application_data.user_id, + email: application_data.user_email, + zid: application_data.user_zid, + name: application_data.user_name, + pronouns: application_data.user_pronouns, + gender: application_data.user_gender, + degree_name: application_data.user_degree_name, + degree_starting_year: application_data.user_degree_starting_year, + }, + } + ) + } + + + /* + Get All applications that apply for a given role + */ + pub async fn get_from_role_id(role_id: i64, transaction: &mut Transaction<'_, Postgres>,) + -> Result, ChaosError> { + let application_data_list = sqlx::query_as!( + ApplicationData, + " + SELECT a.id AS id, campaign_id, user_id, status AS \"status: ApplicationStatus\", + private_status AS \"private_status: ApplicationStatus\", u.email AS user_email, + u.zid AS user_zid, u.name AS user_name, u.gender AS user_gender, + u.pronouns AS user_pronouns, u.degree_name AS user_degree_name, + u.degree_starting_year AS user_degree_starting_year + FROM applications a LEFT JOIN users u ON u.id = a.user_id LEFT JOIN application_roles ar on ar.application_id = a.id + WHERE ar.id = $1 + ", + role_id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let mut application_details_list = Vec::new(); + for application_data in application_data_list { + let applied_roles = sqlx::query_as!( + ApplicationAppliedRoleDetails, + " + SELECT application_roles.campaign_role_id, campaign_roles.name AS role_name + FROM application_roles + LEFT JOIN campaign_roles + ON application_roles.campaign_role_id = campaign_roles.id + WHERE application_id = $1 + ", + application_data.id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let details = ApplicationDetails { + id: application_data.id, + campaign_id: application_data.campaign_id, + status: application_data.status, + private_status: application_data.private_status, + applied_roles, + user: UserDetails { + id: application_data.user_id, + email: application_data.user_email, + zid: application_data.user_zid, + name: application_data.user_name, + pronouns: application_data.user_pronouns, + gender: application_data.user_gender, + degree_name: application_data.user_degree_name, + degree_starting_year: application_data.user_degree_starting_year, + }, + }; + + application_details_list.push(details); + } + + Ok(application_details_list) + } + + /* + Get All applications that apply for a given campaign + */ + pub async fn get_from_campaign_id(campaign_id: i64, transaction: &mut Transaction<'_, Postgres>,) + -> Result, ChaosError> { + let application_data_list = sqlx::query_as!( + ApplicationData, + " + SELECT a.id AS id, campaign_id, user_id, status AS \"status: ApplicationStatus\", + private_status AS \"private_status: ApplicationStatus\", u.email AS user_email, + u.zid AS user_zid, u.name AS user_name, u.gender AS user_gender, + u.pronouns AS user_pronouns, u.degree_name AS user_degree_name, + u.degree_starting_year AS user_degree_starting_year + FROM applications a LEFT JOIN users u ON u.id = a.user_id + WHERE a.campaign_id = $1 + ", + campaign_id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let mut application_details_list = Vec::new(); + for application_data in application_data_list { + let applied_roles = sqlx::query_as!( + ApplicationAppliedRoleDetails, + " + SELECT application_roles.campaign_role_id, campaign_roles.name AS role_name + FROM application_roles + LEFT JOIN campaign_roles + ON application_roles.campaign_role_id = campaign_roles.id + WHERE application_id = $1 + ", + application_data.id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let details = ApplicationDetails { + id: application_data.id, + campaign_id: application_data.campaign_id, + status: application_data.status, + private_status: application_data.private_status, + applied_roles, + user: UserDetails { + id: application_data.user_id, + email: application_data.user_email, + zid: application_data.user_zid, + name: application_data.user_name, + pronouns: application_data.user_pronouns, + gender: application_data.user_gender, + degree_name: application_data.user_degree_name, + degree_starting_year: application_data.user_degree_starting_year, + }, + }; + + application_details_list.push(details) + } + + Ok(application_details_list) + } + + /* + Get All applications that are made by a given user + */ + pub async fn get_from_user_id(user_id: i64, transaction: &mut Transaction<'_, Postgres>,) + -> Result, ChaosError> { + let application_data_list = sqlx::query_as!( + ApplicationData, + " + SELECT a.id AS id, campaign_id, user_id, status AS \"status: ApplicationStatus\", + private_status AS \"private_status: ApplicationStatus\", u.email AS user_email, + u.zid AS user_zid, u.name AS user_name, u.gender AS user_gender, + u.pronouns AS user_pronouns, u.degree_name AS user_degree_name, + u.degree_starting_year AS user_degree_starting_year + FROM applications a LEFT JOIN users u ON u.id = a.user_id + WHERE a.user_id = $1 + ", + user_id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let mut application_details_list = Vec::new(); + for application_data in application_data_list { + let applied_roles = sqlx::query_as!( + ApplicationAppliedRoleDetails, + " + SELECT application_roles.campaign_role_id, campaign_roles.name AS role_name + FROM application_roles + LEFT JOIN campaign_roles + ON application_roles.campaign_role_id = campaign_roles.id + WHERE application_id = $1 + ", + application_data.id + ) + .fetch_all(transaction.deref_mut()) + .await?; + + let details = ApplicationDetails { + id: application_data.id, + campaign_id: application_data.campaign_id, + status: application_data.status, + private_status: application_data.private_status, + applied_roles, + user: UserDetails { + id: application_data.user_id, + email: application_data.user_email, + zid: application_data.user_zid, + name: application_data.user_name, + pronouns: application_data.user_pronouns, + gender: application_data.user_gender, + degree_name: application_data.user_degree_name, + degree_starting_year: application_data.user_degree_starting_year, + }, + }; + + application_details_list.push(details) + } + + Ok(application_details_list) + } + + pub async fn set_status(id: i64, new_status: ApplicationStatus, pool: &Pool) -> Result<(), ChaosError> { + sqlx::query!( + " + UPDATE applications + SET status = $2 + WHERE id = $1; + ", + id, + new_status as ApplicationStatus + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn set_private_status(id: i64, new_status: ApplicationStatus, pool: &Pool) -> Result<(), ChaosError> { + sqlx::query!( + " + UPDATE applications + SET private_status = $2 + WHERE id = $1; + ", + id, + new_status as ApplicationStatus + ) + .execute(pool) + .await?; + + Ok(()) + } + +} \ No newline at end of file diff --git a/backend/server/src/models/auth.rs b/backend/server/src/models/auth.rs new file mode 100644 index 00000000..9e1fc040 --- /dev/null +++ b/backend/server/src/models/auth.rs @@ -0,0 +1,279 @@ +use std::collections::HashMap; +use crate::models::app::AppState; +use crate::models::error::ChaosError; +use crate::service::application::user_is_application_admin; +use crate::service::auth::is_super_user; +use crate::service::campaign::user_is_campaign_admin; +use crate::service::jwt::decode_auth_token; +use crate::service::organisation::user_is_organisation_admin; +use crate::service::role::user_is_role_admin; +use axum::extract::{FromRef, FromRequestParts, Path}; +use axum::http::request::Parts; +use axum::response::{IntoResponse, Redirect, Response}; +use axum::{async_trait, RequestPartsExt}; +use axum_extra::{headers::Cookie, TypedHeader}; +use serde::{Deserialize, Serialize}; + +// tells the web framework how to take the url query params they will have +#[derive(Deserialize, Serialize)] +pub struct AuthRequest { + pub code: String, +} + +#[derive(Deserialize, Serialize)] +pub struct GoogleUserProfile { + pub name: String, + pub email: String, +} + +pub struct AuthRedirect; + +impl IntoResponse for AuthRedirect { + fn into_response(self) -> Response { + // TODO: Fix this redirect to point to front end login page + Redirect::temporary("/auth/google").into_response() + } +} + +#[derive(Deserialize, Serialize)] +pub struct AuthUser { + pub user_id: i64, +} + +// extractor - takes a request, and we define what we do to it, +// returns the struct of the type defined +#[async_trait] +impl FromRequestParts for AuthUser +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + Ok(AuthUser { + user_id: claims.sub, + }) + } +} + +#[derive(Deserialize, Serialize)] +pub struct SuperUser { + pub user_id: i64, +} + +#[async_trait] +impl FromRequestParts for SuperUser +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + let pool = &app_state.db; + let possible_user = is_super_user(claims.sub, pool).await; + + if let Ok(is_auth_user) = possible_user { + if is_auth_user { + return Ok(SuperUser { + user_id: claims.sub, + }); + } + } + + Err(ChaosError::Unauthorized) + } +} + +pub struct OrganisationAdmin { + pub user_id: i64, +} + +#[async_trait] +impl FromRequestParts for OrganisationAdmin +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + let pool = &app_state.db; + let user_id = claims.sub; + + let organisation_id = *parts + .extract::>>() + .await + .map_err(|_| ChaosError::BadRequest)? + .get("organisation_id") + .ok_or(ChaosError::BadRequest)?; + + user_is_organisation_admin(user_id, organisation_id, pool).await?; + + Ok(OrganisationAdmin { user_id }) + } +} + +pub struct CampaignAdmin { + pub user_id: i64, +} + +#[async_trait] +impl FromRequestParts for CampaignAdmin +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + let pool = &app_state.db; + let user_id = claims.sub; + + let campaign_id = *parts + .extract::>>() + .await + .map_err(|_| ChaosError::BadRequest)? + .get("campaign_id") + .ok_or(ChaosError::BadRequest)?; + + user_is_campaign_admin(user_id, campaign_id, pool).await?; + + Ok(CampaignAdmin { user_id }) + } +} + +pub struct RoleAdmin { + pub user_id: i64, +} + +#[async_trait] +impl FromRequestParts for RoleAdmin +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + let pool = &app_state.db; + let user_id = claims.sub; + + let role_id = *parts + .extract::>>() + .await + .map_err(|_| ChaosError::BadRequest)? + .get("role_id") + .ok_or(ChaosError::BadRequest)?; + + user_is_role_admin(user_id, role_id, pool).await?; + + Ok(RoleAdmin { user_id }) + } +} + +pub struct ApplicationAdmin { + pub user_id: i64, +} + +#[async_trait] +impl FromRequestParts for ApplicationAdmin +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + let decoding_key = &app_state.decoding_key; + let jwt_validator = &app_state.jwt_validator; + let TypedHeader(cookies) = parts + .extract::>() + .await + .map_err(|_| ChaosError::NotLoggedIn)?; + + let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?; + + let claims = + decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?; + + let pool = &app_state.db; + let user_id = claims.sub; + + let Path(application_id) = parts + .extract::>() + .await + .map_err(|_| ChaosError::BadRequest)?; + + user_is_application_admin(user_id, application_id, pool).await?; + + Ok(ApplicationAdmin { user_id }) + } +} diff --git a/backend/server/src/models/campaign.rs b/backend/server/src/models/campaign.rs new file mode 100644 index 00000000..40590fee --- /dev/null +++ b/backend/server/src/models/campaign.rs @@ -0,0 +1,160 @@ +use chrono::{DateTime, Utc}; +use s3::Bucket; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use sqlx::{Pool, Postgres}; +use uuid::Uuid; + +use super::{error::ChaosError, storage::Storage}; + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct Campaign { + pub id: i64, + pub name: String, + pub organisation_id: i64, + pub organisation_name: String, + pub cover_image: Option, + pub description: Option, + pub starts_at: DateTime, + pub ends_at: DateTime, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct CampaignDetails { + pub id: i64, + pub name: String, + pub organisation_id: i64, + pub organisation_name: String, + pub cover_image: Option, + pub description: Option, + pub starts_at: DateTime, + pub ends_at: DateTime, +} +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct OrganisationCampaign { + pub id: i64, + pub name: String, + pub cover_image: Option, + pub description: Option, + pub starts_at: DateTime, + pub ends_at: DateTime, +} + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct CampaignUpdate { + pub name: String, + pub description: String, + pub starts_at: DateTime, + pub ends_at: DateTime, +} + +#[derive(Serialize)] +pub struct CampaignBannerUpdate { + pub upload_url: String, +} + +impl Campaign { + /// Get a list of all campaigns, both published and unpublished + pub async fn get_all(pool: &Pool) -> Result, ChaosError> { + let campaigns = sqlx::query_as!( + Campaign, + " + SELECT c.*, o.name as organisation_name FROM campaigns c + JOIN organisations o on c.organisation_id = o.id + " + ) + .fetch_all(pool) + .await?; + + Ok(campaigns) + } + + /// Get a campaign based on it's id + pub async fn get(id: i64, pool: &Pool) -> Result { + let campaign = sqlx::query_as!( + CampaignDetails, + " + SELECT c.id, c.name, c.organisation_id, o.name as organisation_name, + c.cover_image, c.description, c.starts_at, c.ends_at + FROM campaigns c + JOIN organisations o on c.organisation_id = o.id + WHERE c.id = $1 + ", + id + ) + .fetch_one(pool) + .await?; + + Ok(campaign) + } + + /// Update a campaign for all fields that are not None + pub async fn update( + id: i64, + update: CampaignUpdate, + pool: &Pool, + ) -> Result<(), ChaosError> { + sqlx::query!( + " + UPDATE campaigns + SET name = $1, description = $2, starts_at = $3, ends_at = $4 + WHERE id = $5 + ", + update.name, + update.description, + update.starts_at, + update.ends_at, + id + ) + .execute(pool) + .await?; + + Ok(()) + } + + /// Update a campaign banner + /// Returns the updated campaign + pub async fn update_banner( + id: i64, + pool: &Pool, + storage_bucket: &Bucket, + ) -> Result { + let dt = Utc::now(); + let image_id = Uuid::new_v4(); + let current_time = dt; + + sqlx::query!( + " + UPDATE campaigns + SET cover_image = $1, updated_at = $2 + WHERE id = $3 + ", + image_id, + current_time, + id + ) + .execute(pool) + .await?; + + let upload_url = + Storage::generate_put_url(format!("/banner/{id}/{image_id}"), storage_bucket).await?; + + Ok(CampaignBannerUpdate { upload_url }) + } + + /// Delete a campaign from the database + pub async fn delete(id: i64, pool: &Pool) -> Result<(), ChaosError> { + sqlx::query!( + " + DELETE FROM campaigns WHERE id = $1 + ", + id + ) + .execute(pool) + .await?; + + Ok(()) + } +} diff --git a/backend/server/src/models/error.rs b/backend/server/src/models/error.rs new file mode 100644 index 00000000..3a2869e3 --- /dev/null +++ b/backend/server/src/models/error.rs @@ -0,0 +1,59 @@ +use axum::http::StatusCode; +use axum::response::{IntoResponse, Redirect, Response}; + +/// Custom error enum for Chaos. +/// +/// Handles all errors thrown by libraries (when `?` is used) alongside +/// specific errors for business logic. +#[derive(thiserror::Error, Debug)] +pub enum ChaosError { + #[error("Not logged in")] + NotLoggedIn, + + #[error("Not authorized")] + Unauthorized, + + #[error("Forbidden operation")] + ForbiddenOperation, + + #[error("Bad request")] + BadRequest, + + #[error("SQLx error")] + DatabaseError(#[from] sqlx::Error), + + #[error("Reqwest error")] + ReqwestError(#[from] reqwest::Error), + + #[error("OAuth2 error")] + OAuthError( + #[from] + oauth2::RequestTokenError< + oauth2::reqwest::Error, + oauth2::StandardErrorResponse, + >, + ), + + #[error("S3 error")] + StorageError(#[from] s3::error::S3Error), +} + +/// Implementation for converting errors into responses. Manages error code and message returned. +impl IntoResponse for ChaosError { + fn into_response(self) -> Response { + match self { + ChaosError::NotLoggedIn => Redirect::temporary("/auth/google").into_response(), + ChaosError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(), + ChaosError::ForbiddenOperation => { + (StatusCode::FORBIDDEN, "Forbidden operation").into_response() + } + ChaosError::BadRequest => (StatusCode::BAD_REQUEST, "Bad request").into_response(), + ChaosError::DatabaseError(db_error) => match db_error { + // We only care about the RowNotFound error, as others are miscellaneous DB errors. + sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Not found").into_response(), + _ => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(), + }, + _ => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(), + } + } +} diff --git a/backend/server/src/models/mod.rs b/backend/server/src/models/mod.rs new file mode 100644 index 00000000..0af7da1a --- /dev/null +++ b/backend/server/src/models/mod.rs @@ -0,0 +1,10 @@ +pub mod app; +pub mod auth; +pub mod campaign; +pub mod error; +pub mod organisation; +pub mod role; +pub mod storage; +pub mod transaction; +pub mod user; +pub mod application; diff --git a/backend/server/src/models/organisation.rs b/backend/server/src/models/organisation.rs new file mode 100644 index 00000000..984deef3 --- /dev/null +++ b/backend/server/src/models/organisation.rs @@ -0,0 +1,345 @@ +use crate::models::campaign::OrganisationCampaign; +use crate::models::error::ChaosError; +use crate::models::storage::Storage; +use chrono::{DateTime, Utc}; +use s3::Bucket; +use serde::{Deserialize, Serialize}; +use snowflake::SnowflakeIdGenerator; +use sqlx::{FromRow, Pool, Postgres, Transaction}; +use std::ops::DerefMut; +use uuid::Uuid; + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct Organisation { + pub id: i64, + pub name: String, + pub logo: Option, + pub created_at: DateTime, + pub updated_at: DateTime, + pub campaigns: Vec, // Awaiting Campaign to be complete - remove comment once done + pub organisation_admins: Vec, +} + +#[derive(Deserialize, Serialize)] +pub struct NewOrganisation { + pub name: String, + pub admin: i64, +} + +#[derive(Deserialize, Serialize)] +pub struct OrganisationDetails { + pub id: i64, + pub name: String, + pub logo: Option, + pub created_at: DateTime, +} + +#[derive(Deserialize, Serialize, sqlx::Type, Clone)] +#[sqlx(type_name = "organisation_role", rename_all = "PascalCase")] +pub enum OrganisationRole { + User, + Admin, +} + +#[derive(Deserialize, Serialize, FromRow)] +pub struct Member { + pub id: i64, + pub name: String, + pub role: OrganisationRole, +} + +#[derive(Deserialize, Serialize)] +pub struct MemberList { + pub members: Vec, +} + +#[derive(Deserialize, Serialize)] +pub struct AdminUpdateList { + pub members: Vec, +} + +#[derive(Deserialize, Serialize)] +pub struct AdminToRemove { + pub user_id: i64, +} + +impl Organisation { + pub async fn create( + admin_id: i64, + name: String, + mut snowflake_generator: SnowflakeIdGenerator, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { + let id = snowflake_generator.generate(); + + sqlx::query!( + " + INSERT INTO organisations (id, name) + VALUES ($1, $2) + ", + id, + name + ) + .execute(transaction.deref_mut()) + .await?; + + sqlx::query!( + " + INSERT INTO organisation_members (organisation_id, user_id, role) + VALUES ($1, $2, $3) + ", + id, + admin_id, + OrganisationRole::Admin as OrganisationRole + ) + .execute(transaction.deref_mut()) + .await?; + + Ok(()) + } + + pub async fn get(id: i64, pool: &Pool) -> Result { + let organisation = sqlx::query_as!( + OrganisationDetails, + " + SELECT id, name, logo, created_at + FROM organisations + WHERE id = $1 + ", + id + ) + .fetch_one(pool) + .await?; + + Ok(organisation) + } + + pub async fn delete(id: i64, pool: &Pool) -> Result<(), ChaosError> { + sqlx::query!( + " + DELETE FROM organisations WHERE id = $1 + ", + id + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn get_admins( + organisation_id: i64, + pool: &Pool, + ) -> Result { + let admin_list = sqlx::query_as!( + Member, + " + SELECT organisation_members.user_id as id, organisation_members.role AS \"role: OrganisationRole\", users.name from organisation_members + LEFT JOIN users on users.id = organisation_members.user_id + WHERE organisation_members.organisation_id = $1 AND organisation_members.role = $2 + ", + organisation_id, + OrganisationRole::Admin as OrganisationRole + ) + .fetch_all(pool) + .await?; + + Ok(MemberList { + members: admin_list, + }) + } + + pub async fn get_members( + organisation_id: i64, + pool: &Pool, + ) -> Result { + let admin_list = sqlx::query_as!( + Member, + " + SELECT organisation_members.user_id as id, organisation_members.role AS \"role: OrganisationRole\", users.name from organisation_members + LEFT JOIN users on users.id = organisation_members.user_id + WHERE organisation_members.organisation_id = $1 + ", + organisation_id + ) + .fetch_all(pool) + .await?; + + Ok(MemberList { + members: admin_list, + }) + } + + pub async fn update_admins( + organisation_id: i64, + admin_id_list: Vec, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { + sqlx::query!( + "DELETE FROM organisation_members WHERE organisation_id = $1 AND role = $2", + organisation_id, + OrganisationRole::Admin as OrganisationRole + ) + .execute(transaction.deref_mut()) + .await?; + + for admin_id in admin_id_list { + sqlx::query!( + " + INSERT INTO organisation_members (organisation_id, user_id, role) + VALUES ($1, $2, $3) + ", + organisation_id, + admin_id, + OrganisationRole::Admin as OrganisationRole + ) + .execute(transaction.deref_mut()) + .await?; + } + + Ok(()) + } + + pub async fn update_members( + organisation_id: i64, + member_id_list: Vec, + transaction: &mut Transaction<'_, Postgres>, + ) -> Result<(), ChaosError> { + sqlx::query!( + "DELETE FROM organisation_members WHERE organisation_id = $1 AND role = $2", + organisation_id, + OrganisationRole::User as OrganisationRole + ) + .execute(transaction.deref_mut()) + .await?; + + for member_id in member_id_list { + sqlx::query!( + " + INSERT INTO organisation_members (organisation_id, user_id, role) + VALUES ($1, $2, $3) + ", + organisation_id, + member_id, + OrganisationRole::User as OrganisationRole + ) + .execute(transaction.deref_mut()) + .await?; + } + + Ok(()) + } + + pub async fn remove_admin( + organisation_id: i64, + admin_to_remove: i64, + pool: &Pool, + ) -> Result<(), ChaosError> { + sqlx::query!( + " + UPDATE organisation_members SET role = $3 WHERE user_id = $1 AND organisation_id = $2 + ", + admin_to_remove, + organisation_id, + OrganisationRole::User as OrganisationRole + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn remove_member( + organisation_id: i64, + user_id: i64, + pool: &Pool, + ) -> Result<(), ChaosError> { + sqlx::query!( + " + DELETE FROM organisation_members WHERE user_id = $1 AND organisation_id = $2 + ", + user_id, + organisation_id + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn update_logo( + id: i64, + pool: &Pool, + storage_bucket: &Bucket, + ) -> Result { + let dt = Utc::now(); + + let logo_id = Uuid::new_v4(); + let current_time = dt; + sqlx::query!( + " + UPDATE organisations + SET logo = $2, updated_at = $3 + WHERE id = $1 + ", + id, + logo_id, + current_time + ) + .execute(pool) + .await?; + + let upload_url = + Storage::generate_put_url(format!("/logo/{id}/{logo_id}"), storage_bucket).await?; + + Ok(upload_url) + } + + pub async fn get_campaigns( + organisation_id: i64, + pool: &Pool, + ) -> Result, ChaosError> { + let campaigns = sqlx::query_as!( + OrganisationCampaign, + " + SELECT id, name, cover_image, description, starts_at, ends_at + FROM campaigns + WHERE organisation_id = $1 + ", + organisation_id + ) + .fetch_all(pool) + .await?; + + Ok(campaigns) + } + + pub async fn create_campaign( + organisation_id: i64, + name: String, + description: Option, + starts_at: DateTime, + ends_at: DateTime, + pool: &Pool, + snowflake_id_generator: &mut SnowflakeIdGenerator, + ) -> Result<(), ChaosError> { + let new_campaign_id = snowflake_id_generator.real_time_generate(); + + sqlx::query!( + " + INSERT INTO campaigns (id, organisation_id, name, description, starts_at, ends_at) + VALUES ($1, $2, $3, $4, $5, $6) + ", + new_campaign_id, + organisation_id, + name, + description, + starts_at, + ends_at + ) + .execute(pool) + .await?; + + Ok(()) + } +} diff --git a/backend/server/src/models/role.rs b/backend/server/src/models/role.rs new file mode 100644 index 00000000..a52eb1d4 --- /dev/null +++ b/backend/server/src/models/role.rs @@ -0,0 +1,142 @@ +use crate::models::error::ChaosError; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use snowflake::SnowflakeIdGenerator; +use sqlx::{FromRow, Pool, Postgres}; + +#[derive(Deserialize, Serialize, Clone, FromRow, Debug)] +pub struct Role { + pub id: i64, + pub campaign_id: i64, + pub name: Option, + pub description: String, + pub min_available: i32, + pub max_avaliable: i32, + pub finalised: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Deserialize, Serialize)] +pub struct RoleUpdate { + pub name: String, + pub description: Option, + pub min_available: i32, + pub max_avaliable: i32, + pub finalised: bool, +} + +#[derive(Deserialize, Serialize)] +pub struct RoleDetails { + pub id: i64, + pub campaign_id: i64, + pub name: String, + pub description: Option, + pub min_available: i32, + pub max_available: i32, + pub finalised: bool, +} + +impl Role { + pub async fn create( + campaign_id: i64, + role_data: RoleUpdate, + pool: &Pool, + mut snowflake_generator: SnowflakeIdGenerator, + ) -> Result<(), ChaosError> { + let id = snowflake_generator.generate(); + + sqlx::query!( + " + INSERT INTO campaign_roles (id, campaign_id, name, description, min_available, max_available, finalised) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ", + id, + campaign_id, + role_data.name, + role_data.description, + role_data.min_available, + role_data.max_avaliable, + role_data.finalised + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn get(id: i64, pool: &Pool) -> Result { + let role = sqlx::query_as!( + RoleDetails, + " + SELECT id, campaign_id, name, description, min_available, max_available, finalised + FROM campaign_roles + WHERE id = $1 + ", + id + ) + .fetch_one(pool) + .await?; + + Ok(role) + } + + pub async fn delete(id: i64, pool: &Pool) -> Result<(), ChaosError> { + sqlx::query!( + " + DELETE FROM campaign_roles WHERE id = $1 + ", + id + ) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn update( + id: i64, + role_data: RoleUpdate, + pool: &Pool, + ) -> Result<(), ChaosError> { + sqlx::query!( + " + UPDATE campaign_roles + SET (name, description, min_available, max_available, finalised) = ($2, $3, $4, $5, $6) + WHERE id = $1; + ", + id, + role_data.name, + role_data.description, + role_data.min_available, + role_data.max_avaliable, + role_data.finalised + ) + .execute(pool) + .await?; + + Ok(()) + } + + /* + Given a campaign id, return all existing roles in this campaign + */ + pub async fn get_all_in_campaign( + campaign_id: i64, + pool: &Pool, + ) -> Result, ChaosError> { + let roles = sqlx::query_as!( + RoleDetails, + " + SELECT id, campaign_id, name, description, min_available, max_available, finalised + FROM campaign_roles + WHERE campaign_id = $1 + ", + campaign_id + ) + .fetch_all(pool) + .await?; + + Ok(roles) + } +} diff --git a/backend/server/src/models/storage.rs b/backend/server/src/models/storage.rs new file mode 100644 index 00000000..d44b35aa --- /dev/null +++ b/backend/server/src/models/storage.rs @@ -0,0 +1,52 @@ +use crate::models::error::ChaosError; +use s3::creds::Credentials; +use s3::{Bucket, Region}; +use std::env; + +pub struct Storage; + +impl Storage { + pub fn init_bucket() -> Bucket { + let bucket_name = env::var("S3_BUCKET_NAME") + .expect("Error getting S3 BUCKET NAME") + .to_string(); + let access_key = env::var("S3_ACCESS_KEY") + .expect("Error getting S3 CREDENTIALS") + .to_string(); + let secret_key = env::var("S3_SECRET_KEY") + .expect("Error getting S3 CREDENTIALS") + .to_string(); + let endpoint = env::var("S3_ENDPOINT") + .expect("Error getting S3 ENDPOINT") + .to_string(); + let region_name = env::var("S3_REGION_NAME") + .expect("Error getting S3 REGION NAME") + .to_string(); + + let credentials = Credentials::new( + Option::from(access_key.as_str()), + Option::from(secret_key.as_str()), + None, + None, + None, + ) + .unwrap(); + + let region = Region::Custom { + region: region_name, + endpoint, + }; + + let bucket = Bucket::new(&*bucket_name, region, credentials).unwrap(); + // TODO: Change depending on style used by provider + // bucket.set_path_style(); + + bucket + } + + pub async fn generate_put_url(path: String, bucket: &Bucket) -> Result { + let url = bucket.presign_put(path, 3600, None).await?; + + Ok(url) + } +} diff --git a/backend/server/src/models/transaction.rs b/backend/server/src/models/transaction.rs new file mode 100644 index 00000000..529f6320 --- /dev/null +++ b/backend/server/src/models/transaction.rs @@ -0,0 +1,27 @@ +use crate::models::app::AppState; +use crate::models::error::ChaosError; +use axum::async_trait; +use axum::extract::{FromRef, FromRequestParts}; +use axum::http::request::Parts; +use sqlx::{Postgres, Transaction}; + +pub struct DBTransaction<'a> { + pub tx: Transaction<'a, Postgres>, +} + +#[async_trait] +impl FromRequestParts for DBTransaction<'_> +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = ChaosError; + + async fn from_request_parts(_: &mut Parts, state: &S) -> Result { + let app_state = AppState::from_ref(state); + + Ok(DBTransaction { + tx: app_state.db.begin().await?, + }) + } +} diff --git a/backend/server/src/models/user.rs b/backend/server/src/models/user.rs new file mode 100644 index 00000000..19009a1f --- /dev/null +++ b/backend/server/src/models/user.rs @@ -0,0 +1,155 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Pool, Postgres}; +use crate::models::error::ChaosError; + +#[derive(Deserialize, Serialize, sqlx::Type, Clone)] +#[sqlx(type_name = "user_role", rename_all = "PascalCase")] +pub enum UserRole { + User, + SuperUser, +} + +#[derive(Deserialize, Serialize, FromRow)] +pub struct UserDetails { + pub id: i64, + pub email: String, + pub zid: Option, + pub name: String, + pub pronouns: String, + pub gender: String, + pub degree_name: Option, + pub degree_starting_year: Option, +} + +#[derive(Deserialize, Serialize, FromRow)] +pub struct User { + pub id: i64, + pub email: String, + pub zid: Option, + pub name: String, + pub pronouns: String, + pub gender: String, + pub degree_name: Option, + pub degree_starting_year: Option, + pub role: UserRole, +} + +#[derive(Deserialize, Serialize)] +pub struct UserName { + pub name: String, +} + +#[derive(Deserialize, Serialize)] +pub struct UserPronouns { + pub pronouns: String, +} + +#[derive(Deserialize, Serialize)] +pub struct UserGender { + pub gender: String, +} + +#[derive(Deserialize, Serialize)] +pub struct UserZid { + pub zid: String, +} + +#[derive(Deserialize, Serialize)] +pub struct UserDegree { + pub degree_name: String, + pub degree_starting_year: i32, +} + +impl User { + pub async fn get(id: i64, pool: &Pool) -> Result { + let user = sqlx::query_as!( + User, + r#" + SELECT id, email, zid, name, pronouns, gender, degree_name, + degree_starting_year, role AS "role!: UserRole" + FROM users WHERE id = $1 + "#, + id + ) + .fetch_one(pool) + .await?; + + Ok(user) + } + + pub async fn update_name(id: i64, name: String, pool: &Pool) -> Result<(), ChaosError> { + let _ = sqlx::query!( + " + UPDATE users SET name = $1 WHERE id = $2 RETURNING id + ", + name, + id + ) + .fetch_one(pool) + .await?; + + Ok(()) + } + + pub async fn update_pronouns(id: i64, pronouns: String, pool: &Pool) -> Result<(), ChaosError> { + let _ = sqlx::query!( + " + UPDATE users SET pronouns = $1 WHERE id = $2 RETURNING id + ", + pronouns, + id + ) + .fetch_one(pool) + .await?; + + Ok(()) + } + + pub async fn update_gender(id: i64, gender: String, pool: &Pool) -> Result<(), ChaosError> { + let _ = sqlx::query!( + " + UPDATE users SET gender = $1 WHERE id = $2 RETURNING id + ", + gender, + id + ) + .fetch_one(pool) + .await?; + + Ok(()) + } + + pub async fn update_zid(id: i64, zid: String, pool: &Pool) -> Result<(), ChaosError> { + let _ = sqlx::query!( + " + UPDATE users SET zid = $1 WHERE id = $2 RETURNING id + ", + zid, + id + ) + .fetch_one(pool) + .await?; + + Ok(()) + } + + pub async fn update_degree( + id: i64, + degree_name: String, + degree_starting_year: i32, + pool: &Pool, + ) -> Result<(), ChaosError> { + let _ = sqlx::query!( + " + UPDATE users SET degree_name = $1, degree_starting_year = $2 WHERE id = $3 RETURNING id + ", + degree_name, + degree_starting_year, + id + ) + .fetch_one(pool) + .await?; + + Ok(()) + } +} diff --git a/backend/server/src/organisation.rs b/backend/server/src/organisation.rs deleted file mode 100644 index 7088947d..00000000 --- a/backend/server/src/organisation.rs +++ /dev/null @@ -1,375 +0,0 @@ -use crate::error::JsonErr; -use crate::images::get_image_path; -use crate::{ - database::{ - models::{ - Campaign, NewOrganisation, NewOrganisationUser, Organisation, OrganisationUser, - SuperUser, User, - }, - schema::AdminLevel, - Database, - }, - images::{get_http_image_path, save_image, try_decode_data, ImageLocation}, -}; -use chrono::NaiveDateTime; -use rocket::{ - data::Data, - delete, get, - http::Status, - post, put, - serde::{json::Json, Deserialize, Serialize}, -}; -use std::collections::HashMap; -use std::fs::remove_file; -use uuid::Uuid; - -#[derive(Serialize)] -pub enum NewOrgError { - OrgNameAlreadyExists, - FailedToJoin, -} - -#[derive(Serialize)] -pub enum OrgError { - OrgNotFound, - InsufficientPerms, - UserIsNotInOrg, - UserNotFound, - UserAlreadyInOrg, - Unknown, -} - -#[post("/", data = "")] -pub async fn new( - organisation: Json, - user: SuperUser, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let org = NewOrganisation::insert(&organisation, &conn) - .ok_or(JsonErr(NewOrgError::OrgNameAlreadyExists, Status::NotFound))?; - - let org_user = NewOrganisationUser { - user_id: user.user().id, - organisation_id: org.id, - admin_level: AdminLevel::Admin, - }; - - org_user - .insert(conn) - .ok_or(JsonErr(NewOrgError::FailedToJoin, Status::Forbidden))?; - - Ok(Json(org)) - }) - .await -} - -// ============ /organisation/ ============ - -#[get("/")] -pub async fn get_from_id( - org_id: i32, - _user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - Organisation::get_from_id(&conn, org_id) - .ok_or(JsonErr(OrgError::OrgNotFound, Status::NotFound)) - .map(|mut v| { - v.logo = v - .logo - .map(|logo_uuid| get_http_image_path(ImageLocation::ORGANISATIONS, &logo_uuid)); - Json(v) - }) - }) - .await -} - -#[get("/", data = "")] -pub async fn get_from_ids( - orgs: Json>, - _user: User, - db: Database, -) -> Result>, JsonErr> { - db.run(move |conn| { - let mut res = HashMap::with_capacity(orgs.len()); - - for id in orgs.into_inner() { - res.insert( - id, - Organisation::get_from_id(&conn, id) - .ok_or(JsonErr(OrgError::OrgNotFound, Status::NotFound))?, - ); - } - - Ok(Json(res)) - }) - .await -} - -#[delete("/")] -pub async fn delete(org_id: i32, _user: SuperUser, db: Database) -> Result<(), JsonErr> { - db.run(move |conn| { - Organisation::delete_deep(&conn, org_id) - .ok_or(JsonErr(OrgError::OrgNotFound, Status::NotFound)) - }) - .await -} - -// ============ /organisation//superusers ============ - -#[get("//superusers")] -pub async fn get_admins( - org_id: i32, - _user: User, - db: Database, -) -> Result>, JsonErr> { - let res = db - .run(move |conn| Organisation::get_admin_ids(&conn, org_id)) - .await; - - match res { - Some(ids) => Ok(Json(ids)), - None => Err(JsonErr(OrgError::OrgNotFound, Status::NotFound)), - } -} - -#[derive(Serialize)] -pub enum LogoError { - Unauthorized, - ImageDeletionFailure, - ImageStoreFailure, -} - -#[put("//logo", data = "")] -pub async fn set_logo( - org_id: i32, - user: User, - db: Database, - image: Data<'_>, -) -> Result, JsonErr> { - db.run(move |conn| { - OrganisationUser::organisation_admin_level(org_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(JsonErr(LogoError::Unauthorized, Status::Forbidden))) - }) - .await?; - - let old_logo_uuid = db - .run(move |conn| Organisation::get_logo(&conn, org_id)) - .await; - let logo_uuid = Uuid::new_v4().as_hyphenated().to_string() + ".webp"; - - let image = try_decode_data(image).await.or_else(|_| { - Err(JsonErr( - LogoError::ImageDeletionFailure, - Status::InternalServerError, - )) - })?; - - save_image(image, ImageLocation::ORGANISATIONS, &logo_uuid) - .map_err(|_| JsonErr(LogoError::ImageStoreFailure, Status::InternalServerError))?; - - let logo_uuid_clone = logo_uuid.clone(); - - db.run(move |conn| Organisation::set_logo(&conn, org_id, &logo_uuid_clone)) - .await; - - if let Some(uuid) = old_logo_uuid { - remove_file(get_image_path(ImageLocation::ORGANISATIONS, &uuid)).ok(); - } - - Ok(Json(get_http_image_path( - ImageLocation::ORGANISATIONS, - &logo_uuid, - ))) -} - -#[put("//admins", data = "")] -pub async fn set_admins( - org_id: i32, - user: User, - db: Database, - admins: Json>, -) -> Result, JsonErr> { - let res = db - .run(move |conn| Organisation::get_admin_ids(&conn, org_id)) - .await; - - match res { - Some(ids) => { - if !ids.contains(&user.id) { - return Err(JsonErr(OrgError::InsufficientPerms, Status::Forbidden)); - } else { - db.run(move |conn| Organisation::set_admins(&conn, org_id, &admins)) - .await; - Ok(Json(())) - } - } - - None => Err(JsonErr(OrgError::OrgNotFound, Status::NotFound)), - } -} - -#[get("//is_admin")] -pub async fn is_admin(org_id: i32, user: User, db: Database) -> Json { - let res = db - .run(move |conn| Organisation::get_admin_ids(&conn, org_id)) - .await; - - match res { - Some(ids) => Json(ids.contains(&user.id) || user.superuser), - None => Json(false), - } -} - -#[derive(Serialize)] -pub struct CampaignResponse { - pub id: i32, - pub name: String, - pub cover_image: Option, - pub description: String, - pub starts_at: NaiveDateTime, - pub ends_at: NaiveDateTime, - pub published: bool, -} - -impl std::convert::From for CampaignResponse { - fn from(campaign: Campaign) -> Self { - Self { - id: campaign.id, - name: campaign.name, - cover_image: campaign - .cover_image - .map(|image| get_http_image_path(ImageLocation::CAMPAIGNS, &image)), - description: campaign.description, - starts_at: campaign.starts_at, - ends_at: campaign.ends_at, - published: campaign.published, - } - } -} - -#[derive(Serialize)] -pub struct GetCampaignsResponse { - campaigns: Vec, -} - -#[get("//campaigns")] -pub async fn get_associated_campaigns( - org_id: i32, - user: User, - db: Database, -) -> Json { - db.run(move |conn| { - let is_director = OrganisationUser::organisation_admin_level(org_id, user.id, conn) - .is_at_least_director() - .check() - .is_ok(); - - Json(GetCampaignsResponse { - campaigns: Campaign::get_all_from_org_id(conn, org_id) - .into_iter() - .filter(|v| v.published || is_director) - .map(CampaignResponse::from) - .collect(), - }) - }) - .await -} - -#[derive(Serialize, Deserialize)] -pub struct EmailInvite { - pub email: String, - pub admin_level: AdminLevel, -} - -#[post("//invite", data = "")] -pub async fn invite_email( - organisation_id: i32, - input: Json, - user: User, - db: Database, -) -> Result<(), JsonErr> { - let EmailInvite { email, admin_level } = input.into_inner(); - db.run(move |conn| { - let mut level = OrganisationUser::organisation_admin_level(organisation_id, user.id, conn) - .check() - .map_err(|_| JsonErr(OrgError::InsufficientPerms, Status::Forbidden))? - .0; - - if user.superuser { - level = AdminLevel::Admin; - } - - let invitee = User::get_from_email(conn, &email) - .ok_or(JsonErr(OrgError::UserNotFound, Status::NotFound))?; - - if level.geq(admin_level) { - let new_user = NewOrganisationUser { - user_id: invitee.id, - organisation_id, - admin_level, - }; - - new_user - .insert(conn) - .map(|_| ()) - .ok_or(JsonErr(OrgError::UserAlreadyInOrg, Status::NotAcceptable)) - } else { - Err(JsonErr(OrgError::InsufficientPerms, Status::Forbidden)) - } - }) - .await -} - -#[post("//invite/", data = "")] -pub async fn invite_uid( - organisation_id: i32, - admin_level: Json, - user_id: i32, - user: User, - db: Database, -) -> Result<(), JsonErr> { - db.run(move |conn| { - let mut level = OrganisationUser::organisation_admin_level(organisation_id, user.id, conn) - .check() - .map_err(|_| JsonErr(OrgError::InsufficientPerms, Status::Forbidden))? - .0; - - if user.superuser { - level = AdminLevel::Admin; - } - - let admin_level = admin_level.into_inner(); - - if level.geq(admin_level) { - match OrganisationUser::get(conn, organisation_id, user_id) { - Some(u) => { - if u.admin_level.geq(u.admin_level) { - Err(JsonErr(OrgError::InsufficientPerms, Status::Forbidden)) - } else { - u.update_admin_level(conn, admin_level) - .ok_or(JsonErr(OrgError::Unknown, Status::InternalServerError)) - } - } - None => { - let new_user = NewOrganisationUser { - user_id, - organisation_id, - admin_level, - }; - - new_user - .insert(conn) - .map(|_| ()) - .ok_or(JsonErr(OrgError::UserAlreadyInOrg, Status::NotAcceptable)) - } - } - } else { - Err(JsonErr(OrgError::InsufficientPerms, Status::Forbidden)) - } - }) - .await -} diff --git a/backend/server/src/permissions.rs b/backend/server/src/permissions.rs deleted file mode 100644 index 2b23abdf..00000000 --- a/backend/server/src/permissions.rs +++ /dev/null @@ -1,254 +0,0 @@ -use crate::database::models::OrganisationUser; -use crate::database::schema::*; -use diesel::{ - expression_methods::ExpressionMethods, query_dsl::QueryDsl, JoinOnDsl, PgConnection, - RunQueryDsl, -}; - -/* -Permission Documentation - -The implmentation below is designed to be used in this pattern: - - db.run(move |conn| { - let campaign = Campaign::get_from_id(conn, campaign_id) - .ok_or_else(|| Json(RolesError::CampaignNotFound))?; - - OrganisationUser::campaign_admin_level(campaign_id, user.id, &conn) - .is_at_least_director() - .and(campaign.draft) // is at least director AND campaign is a draft - .check() - .or_else(|_| Err(Jsorganisation_users::on(RolesError::Unauthorized)))?; - }).await - -This allows you to search for different admin levels while keeping everything in one place. - - -Note on repeated code in this file: - -I originally wanted to do a dynamic set of inner joins, then execute in a pattern like this: -(this would have been implemented with BoxedExpression) - - db.run(move |conn| { - let campaign = Campaign::get_from_id(conn, campaign_id) - .ok_or_else(|| Json(RolesError::CampaignNotFound))?; - - OrganisationUser::campaign_admin_level(campaign_id, user.id) - .is_at_least_director() - .and(campaign.draft) // is at least director AND campaign is a draft - .check(&conn) - .or_else(|_| Err(Json(RolesError::Unauthorized)))?; - }).await - -However, diesel doesn't yet support boxed queries with inner joins (only single tables) -At the point at which this is supported, this file can be cleaned up massively and the above -syntax can be implemented (oh woe the youth of the rust language) -*/ - -pub enum PermissionError { - Unauthorized, - ConditionNotMet, -} - -pub struct AdminLevelUser { - // (admin_level, is_superuser) - res: Result<(AdminLevel, bool), PermissionError>, -} - -impl AdminLevelUser { - pub fn is_at_least_director(self) -> AdminLevelUser { - match self.res { - Ok((_, true)) | Ok((AdminLevel::Admin, false)) | Ok((AdminLevel::Director, false)) => { - self - } - _ => AdminLevelUser { - res: Err(PermissionError::Unauthorized), - }, - } - } - - pub fn is_admin(self) -> AdminLevelUser { - match self.res { - Ok((_, true)) | Ok((AdminLevel::Admin, false)) => self, - _ => AdminLevelUser { - res: Err(PermissionError::Unauthorized), - }, - } - } - - pub fn is_superuser(self) -> AdminLevelUser { - match self.res { - Ok((_, true)) => self, - _ => AdminLevelUser { - res: Err(PermissionError::Unauthorized), - }, - } - } - - pub fn and(self, condition: bool) -> AdminLevelUser { - match condition { - true => self, - false => AdminLevelUser { - res: Err(PermissionError::ConditionNotMet), - }, - } - } - - pub fn or(self, condition: bool) -> AdminLevelUser { - match condition { - true => self, - false => match self.res { - Ok((_, _)) => self, - _ => AdminLevelUser { - res: Err(PermissionError::ConditionNotMet), - }, - }, - } - } - - pub fn check(self) -> Result<(AdminLevel, bool), PermissionError> { - self.res - } -} - -impl std::convert::From> for AdminLevelUser { - fn from(res: Result<(AdminLevel, bool), PermissionError>) -> Self { - AdminLevelUser { res } - } -} - -fn is_superuser(user_id: i32, conn: &PgConnection) -> bool { - match users::table - .filter(users::id.eq(user_id)) - .select(users::superuser) - .first(conn) - .ok() - { - Some(true) => true, - _ => false, - } -} - -impl OrganisationUser { - pub fn organisation_admin_level( - org_id: i32, - user_id: i32, - conn: &PgConnection, - ) -> AdminLevelUser { - if is_superuser(user_id, conn) { - return AdminLevelUser { - res: Ok((AdminLevel::Admin, true)), - }; - } - organisation_users::table - .filter(organisation_users::organisation_id.eq(org_id)) - .inner_join(users::table.on(users::id.eq(organisation_users::user_id))) - .filter(users::id.eq(user_id)) - .select((organisation_users::admin_level, users::superuser)) - .first(conn) - .or_else(|_| Err(PermissionError::Unauthorized)) - .into() - } - - pub fn campaign_admin_level( - campaign_id: i32, - user_id: i32, - conn: &PgConnection, - ) -> AdminLevelUser { - if is_superuser(user_id, conn) { - return AdminLevelUser { - res: Ok((AdminLevel::Admin, true)), - }; - } - campaigns::table - .filter(campaigns::id.eq(campaign_id)) - .inner_join(organisations::table.on(organisations::id.eq(campaigns::organisation_id))) - .inner_join( - organisation_users::table - .on(organisation_users::organisation_id.eq(organisations::id)), - ) - .inner_join(users::table.on(users::id.eq(organisation_users::user_id))) - .filter(users::id.eq(user_id)) - .select((organisation_users::admin_level, users::superuser)) - .first(conn) - .or_else(|_| Err(PermissionError::Unauthorized)) - .into() - } - - pub fn application_admin_level( - application_id: i32, - user_id: i32, - conn: &PgConnection, - ) -> AdminLevelUser { - if is_superuser(user_id, conn) { - return AdminLevelUser { - res: Ok((AdminLevel::Admin, true)), - }; - } - applications::table - .filter(applications::id.eq(application_id)) - .inner_join(roles::table.on(roles::id.eq(applications::role_id))) - .inner_join(campaigns::table.on(campaigns::id.eq(roles::campaign_id))) - .inner_join(organisations::table.on(organisations::id.eq(campaigns::organisation_id))) - .inner_join( - organisation_users::table - .on(organisation_users::organisation_id.eq(organisations::id)), - ) - .inner_join(users::table.on(users::id.eq(organisation_users::user_id))) - .filter(users::id.eq(user_id)) - .select((organisation_users::admin_level, users::superuser)) - .first(conn) - .or_else(|_| Err(PermissionError::Unauthorized)) - .into() - } - - pub fn role_admin_level(role_id: i32, user_id: i32, conn: &PgConnection) -> AdminLevelUser { - if is_superuser(user_id, conn) { - return AdminLevelUser { - res: Ok((AdminLevel::Admin, true)), - }; - } - roles::table - .filter(roles::id.eq(role_id)) - .inner_join(campaigns::table.on(campaigns::id.eq(roles::campaign_id))) - .inner_join(organisations::table.on(organisations::id.eq(campaigns::organisation_id))) - .inner_join( - organisation_users::table - .on(organisation_users::organisation_id.eq(organisations::id)), - ) - .inner_join(users::table.on(users::id.eq(organisation_users::user_id))) - .filter(users::id.eq(user_id)) - .select((organisation_users::admin_level, users::superuser)) - .first(conn) - .or_else(|_| Err(PermissionError::Unauthorized)) - .into() - } - - pub fn comment_admin_level( - comment_id: i32, - user_id: i32, - conn: &PgConnection, - ) -> AdminLevelUser { - if is_superuser(user_id, conn) { - return AdminLevelUser { - res: Ok((AdminLevel::Admin, true)), - }; - } - comments::table - .filter(comments::id.eq(comment_id)) - .inner_join(applications::table.on(applications::id.eq(comments::application_id))) - .inner_join(roles::table.on(roles::id.eq(applications::role_id))) - .inner_join(campaigns::table.on(campaigns::id.eq(roles::campaign_id))) - .inner_join(organisations::table.on(organisations::id.eq(campaigns::organisation_id))) - .inner_join( - organisation_users::table - .on(organisation_users::organisation_id.eq(organisations::id)), - ) - .inner_join(users::table.on(users::id.eq(organisation_users::user_id))) - .filter(users::id.eq(user_id)) - .select((organisation_users::admin_level, users::superuser)) - .first(conn) - .or_else(|_| Err(PermissionError::Unauthorized)) - .into() - } -} diff --git a/backend/server/src/question.rs b/backend/server/src/question.rs deleted file mode 100644 index d45bc56f..00000000 --- a/backend/server/src/question.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::database::{ - models::{ - Campaign, OrganisationUser, Question, QuestionResponse, Role, UpdateQuestionInput, User, - }, - Database, -}; -use crate::error::JsonErr; - -use rocket::{ - delete, get, - http::Status, - put, - serde::{json::Json, Serialize}, -}; - -use std::convert::From; - -#[derive(Serialize)] -pub enum QuestionError { - QuestionNotFound, - UpdateFailed, - InsufficientPermissions, -} - -// TODO: may be useless function, also awfully inefficient. -#[get("/")] -pub async fn get_question( - user: User, - db: Database, - question_id: i32, -) -> Result, JsonErr> { - db.run(move |conn| { - let q = Question::get_from_id(&conn, question_id) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - let r = Role::get_from_id(&conn, q.get_first_role()) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - let c = Campaign::get_from_id(&conn, r.campaign_id) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - OrganisationUser::role_admin_level(q.get_first_role(), user.id, conn) - .is_at_least_director() - .or(c.published) - .check() - .map_err(|_| JsonErr(QuestionError::InsufficientPermissions, Status::Forbidden))?; - Ok(q) - }) - .await - .map(|q| Json(QuestionResponse::from(q))) -} - -#[put("/", data = "")] -pub async fn edit_question( - db: Database, - question_id: i32, - update_question: Json, - user: User, -) -> Result<(), JsonErr> { - db.run(move |conn| { - let question = Question::get_from_id(&conn, question_id) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - let role = Role::get_from_id(conn, question.get_first_role()) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - - OrganisationUser::role_admin_level(role.id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| { - Err(JsonErr( - QuestionError::InsufficientPermissions, - Status::Forbidden, - )) - })?; - - Question::update(&conn, question_id, update_question.into_inner()).ok_or(JsonErr( - QuestionError::UpdateFailed, - Status::InternalServerError, - )) - }) - .await -} - -#[delete("/")] -pub async fn delete_question( - db: Database, - question_id: i32, - user: User, -) -> Result<(), JsonErr> { - db.run(move |conn| { - let question = Question::get_from_id(&conn, question_id) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - let role = Role::get_from_id(conn, question.get_first_role()) - .ok_or(JsonErr(QuestionError::QuestionNotFound, Status::NotFound))?; - - OrganisationUser::role_admin_level(role.id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| { - Err(JsonErr( - QuestionError::InsufficientPermissions, - Status::Forbidden, - )) - })?; - - if Question::delete(&conn, question_id) { - Ok(()) - } else { - Err(JsonErr( - QuestionError::UpdateFailed, - Status::InternalServerError, - )) - } - }) - .await -} diff --git a/backend/server/src/role.rs b/backend/server/src/role.rs deleted file mode 100644 index 51426c84..00000000 --- a/backend/server/src/role.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::database::{ - models::{ - Application, Campaign, GetQuestionsResponse, OrganisationUser, Question, Role, RoleUpdate, - User, - }, - schema::ApplicationStatus, - Database, -}; -use chrono::NaiveDateTime; -use diesel::PgConnection; -use rocket::{ - delete, get, post, put, - serde::{json::Json, Serialize}, -}; - -#[derive(Serialize)] -pub enum RoleError { - RoleUpdateFailure, - RoleNotFound, - CampaignNotFound, - Unauthorized, - RoleAlreadyExists, -} - -#[derive(Serialize)] -pub struct RoleResponse { - name: String, - description: Option, - min_available: i32, - max_available: i32, -} - -impl From for RoleResponse { - fn from(role: Role) -> Self { - RoleResponse { - name: role.name, - description: role.description, - min_available: role.min_available, - max_available: role.max_available, - } - } -} - -#[get("/")] -pub async fn get_role( - role_id: i32, - _user: User, - db: Database, -) -> Result, Json> { - let res = db.run(move |conn| Role::get_from_id(&conn, role_id)).await; - - match res { - Some(role) => Ok(Json(RoleResponse::from(role))), - None => Err(Json(RoleError::RoleNotFound)), - } -} - -#[put("/", data = "")] -pub async fn update_role( - role_id: i32, - role_update: Json, - user: User, - db: Database, -) -> Result, Json> { - db.run(move |conn| { - OrganisationUser::role_admin_level(role_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(Json(RoleError::Unauthorized)))?; - - let role = Role::update(conn, role_id, &role_update) - .ok_or_else(|| Json(RoleError::RoleUpdateFailure))?; - - Ok(Json(role.into())) - }) - .await -} - -#[delete("/")] -pub async fn delete_role(role_id: i32, user: User, db: Database) -> Result<(), Json> { - db.run(move |conn| { - OrganisationUser::role_admin_level(role_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(Json(RoleError::Unauthorized)))?; - - Role::delete_deep(conn, role_id).ok_or_else(|| Json(RoleError::RoleUpdateFailure))?; - - Ok(()) - }) - .await -} - -#[post("/", data = "")] -pub async fn new_role( - role: Json, - user: User, - db: Database, -) -> Result<(), Json> { - db.run(move |conn| { - OrganisationUser::campaign_admin_level(role.campaign_id, user.id, &conn) - .is_at_least_director() - .check() - .or_else(|_| Err(Json(RoleError::Unauthorized)))?; - - RoleUpdate::insert(&role, &conn).ok_or_else(|| Json(RoleError::RoleAlreadyExists))?; - - Ok(()) - }) - .await -} - -#[derive(Serialize)] -pub enum QuestionsError { - RoleNotFound, - CampaignNotFound, - Unauthorized, - UserNotFound, -} - -#[get("//questions")] -pub async fn get_questions( - role_id: i32, - user: User, - db: Database, -) -> Result, Json> { - // First check that the role is valid and the user should be able to access the ids. - // We can't use the helper function below since behaviour depends on the draft - // status of the campaign. - - db.run(move |conn| { - let role = Role::get_from_id(conn, role_id).ok_or(Json(QuestionsError::RoleNotFound))?; - let campaign = Campaign::get_from_id(conn, role.campaign_id) - .ok_or(Json(QuestionsError::CampaignNotFound))?; - - // Prevent people from viewing while it's in draft mode, - // unless they have adequate permissions - OrganisationUser::campaign_admin_level(campaign.id, user.id, &conn) - .is_at_least_director() - .or(campaign.published) - .check() - .map_err(|_| Json(QuestionsError::Unauthorized))?; - Ok(Json(GetQuestionsResponse { - questions: Question::get_all_from_role_id(conn, role_id) - .into_iter() - .map(|x| x.into()) - .collect(), - })) - }) - .await -} - -#[derive(Serialize)] -pub struct ApplicationResponse { - pub id: i32, - pub user_id: i32, - pub user_email: String, - pub user_zid: String, - pub user_display_name: String, - pub user_degree_name: String, - pub user_degree_starting_year: i32, - pub role_id: i32, - pub status: ApplicationStatus, - pub private_status: ApplicationStatus, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} - -impl ApplicationResponse { - pub fn from_application(app: Application, conn: &PgConnection) -> Option { - let user = User::get_from_id(conn, app.user_id)?; - - Some(ApplicationResponse { - id: app.id, - user_id: app.user_id, - user_display_name: user.display_name, - user_email: user.email, - user_degree_name: user.degree_name, - user_degree_starting_year: user.degree_starting_year, - user_zid: user.zid, - role_id: app.role_id, - status: app.status, - private_status: app.private_status, - created_at: app.created_at, - updated_at: app.updated_at, - }) - } -} - -#[derive(Serialize)] -pub struct GetApplicationsResponse { - pub applications: Vec, -} - -#[get("//applications")] -pub async fn get_applications( - role_id: i32, - user: User, - db: Database, -) -> Result, Json> { - // First check that the role is valid and the user should be able to access the ids. - // We can't use the helper function below since behaviour depends on the draft - // status of the campaign. - - db.run(move |conn| { - // NOTE: admin_level doesn't give good error info when eg. role is not found - // (just says unauthorized) - OrganisationUser::role_admin_level(role_id, user.id, conn) - .is_at_least_director() - .check() - .map_err(|_| QuestionsError::Unauthorized)?; - - let apps = Application::get_all_from_role_id(conn, role_id); - let mut res_vec = Vec::with_capacity(apps.len()); - - for app in apps { - res_vec.push( - ApplicationResponse::from_application(app, conn) - .ok_or(Json(QuestionsError::UserNotFound))?, - ); - } - - Ok(Json(GetApplicationsResponse { - applications: res_vec, - })) - }) - .await -} diff --git a/backend/server/src/schema.jpg b/backend/server/src/schema.jpg deleted file mode 100644 index 6bf1d5aa..00000000 Binary files a/backend/server/src/schema.jpg and /dev/null differ diff --git a/backend/server/src/schema.png b/backend/server/src/schema.png deleted file mode 100644 index d075a240..00000000 Binary files a/backend/server/src/schema.png and /dev/null differ diff --git a/backend/server/src/service/application.rs b/backend/server/src/service/application.rs new file mode 100644 index 00000000..4fdf052b --- /dev/null +++ b/backend/server/src/service/application.rs @@ -0,0 +1,36 @@ +use crate::models::error::ChaosError; +use sqlx::{Pool, Postgres}; + + +pub async fn user_is_application_admin( + user_id: i64, + application_id: i64, + pool: &Pool, +) -> Result<(), ChaosError> { + let is_admin = sqlx::query!( + " + SELECT EXISTS( + SELECT 1 FROM ( + SELECT c.organisation_id FROM applications a + JOIN campaigns c on a.campaign_id = c.id + WHERE a.id = $1 + ) ca + JOIN organisation_members m on ca.organisation_id = m.organisation_id + WHERE m.user_id = $2 AND m.role = 'Admin' + ) + ", + application_id, + user_id + ) + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); + + if !is_admin { + return Err(ChaosError::Unauthorized); + } + + Ok(()) + +} \ No newline at end of file diff --git a/backend/server/src/service/auth.rs b/backend/server/src/service/auth.rs new file mode 100644 index 00000000..cf136cfd --- /dev/null +++ b/backend/server/src/service/auth.rs @@ -0,0 +1,52 @@ +use crate::models::user::UserRole; +use anyhow::Result; +use snowflake::SnowflakeIdGenerator; +use sqlx::{Pool, Postgres}; + +/// Checks if a user exists in DB based on given email address. If so, their user_id is returned. +/// Otherwise, a new user is created in the DB, and the new id is returned. +/// This function is used in OAuth flows to login/signup users when they click the +/// "Sign in with ___" buttons. The returned user_id will be used to generate a JWT to be +/// used as a token for the user's browser. +pub async fn create_or_get_user_id( + email: String, + name: String, + pool: Pool, + mut snowflake_generator: SnowflakeIdGenerator, +) -> Result { + let possible_user_id = sqlx::query!( + "SELECT id FROM users WHERE lower(email) = $1", + email.to_lowercase() + ) + .fetch_optional(&pool) + .await?; + + if let Some(result) = possible_user_id { + return Ok(result.id); + } + + let user_id = snowflake_generator.real_time_generate(); + + sqlx::query!( + "INSERT INTO users (id, email, name) VALUES ($1, $2, $3)", + user_id, + email.to_lowercase(), + name + ) + .execute(&pool) + .await?; + + Ok(user_id) +} + +pub async fn is_super_user(user_id: i64, pool: &Pool) -> Result { + let is_super_user = sqlx::query!( + "SELECT EXISTS(SELECT 1 FROM users WHERE id = $1 AND role = $2)", + user_id, + UserRole::SuperUser as UserRole + ) + .fetch_one(pool) + .await?; + + Ok(is_super_user.exists.unwrap()) +} diff --git a/backend/server/src/service/campaign.rs b/backend/server/src/service/campaign.rs new file mode 100644 index 00000000..b9551473 --- /dev/null +++ b/backend/server/src/service/campaign.rs @@ -0,0 +1,30 @@ +use crate::models::error::ChaosError; +use sqlx::{Pool, Postgres}; + +pub async fn user_is_campaign_admin( + user_id: i64, + campaign_id: i64, + pool: &Pool, +) -> Result<(), ChaosError> { + let is_admin = sqlx::query!( + " + SELECT EXISTS( + SELECT 1 FROM campaigns c + JOIN organisation_members m on c.organisation_id = m.organisation_id + WHERE c.organisation_id = $1 AND m.user_id = $2 AND m.role = 'Admin' + ) + ", + campaign_id, + user_id + ) + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); + + if !is_admin { + return Err(ChaosError::Unauthorized); + } + + Ok(()) +} diff --git a/backend/server/src/service/jwt.rs b/backend/server/src/service/jwt.rs new file mode 100644 index 00000000..bddce804 --- /dev/null +++ b/backend/server/src/service/jwt.rs @@ -0,0 +1,55 @@ +use jsonwebtoken::DecodingKey; +use jsonwebtoken::{decode, encode, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthorizationJwtPayload { + pub iss: String, // issuer + pub sub: i64, // subject (user's id) + pub jti: Uuid, // id + pub aud: Vec, // audience (uri the JWT is meant for) + + // Time-based validity + pub exp: i64, // expiry (UNIX timestamp) + pub nbf: i64, // not-valid-before (UNIX timestamp) + pub iat: i64, // issued-at (UNIX timestamp) + + pub username: String, // username +} + +pub fn encode_auth_token( + username: String, + user_id: i64, + encoding_key: &EncodingKey, + jwt_header: &Header, +) -> String { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let expiry = i64::try_from((current_time + Duration::from_secs(604800)).as_secs()).unwrap(); + let claims = AuthorizationJwtPayload { + iss: "Chaos".to_string(), + sub: user_id, + jti: Uuid::new_v4(), + aud: vec!["chaos.devsoc.app".to_string()], + exp: expiry, + nbf: i64::try_from(current_time.as_secs()).unwrap(), + iat: i64::try_from(current_time.as_secs()).unwrap(), + username, + }; + + encode(jwt_header, &claims, encoding_key).unwrap() +} + +pub fn decode_auth_token( + token: &str, + decoding_key: &DecodingKey, + jwt_validator: &Validation, +) -> Option { + let decode_token = decode::(token, decoding_key, jwt_validator); + + match decode_token { + Ok(token) => Option::from(token.claims), + Err(_err) => None::, + } +} diff --git a/backend/server/src/service/mod.rs b/backend/server/src/service/mod.rs new file mode 100644 index 00000000..21940ccd --- /dev/null +++ b/backend/server/src/service/mod.rs @@ -0,0 +1,7 @@ +pub mod auth; +pub mod campaign; +pub mod jwt; +pub mod oauth2; +pub mod organisation; +pub mod role; +pub mod application; diff --git a/backend/server/src/service/oauth2.rs b/backend/server/src/service/oauth2.rs new file mode 100644 index 00000000..43c1104c --- /dev/null +++ b/backend/server/src/service/oauth2.rs @@ -0,0 +1,26 @@ +use oauth2::basic::BasicClient; +use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; +use std::env; + +/// Returns a oauth2::BasicClient, setup with settings for CHAOS Google OAuth. +/// +/// Client follows OAuth2 Standard (https://oauth.net/2/) to get user's email +/// using OpenID Connect (https://openid.net/developers/how-connect-works/). +pub fn build_oauth_client(client_id: String, client_secret: String) -> BasicClient { + let hostname = env::var("CHAOS_HOSTNAME").expect("Could not read CHAOS hostname"); + + let redirect_url = format!("{}/api/auth/callback/google", hostname); + + let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string()) + .expect("Invalid authorization endpoint URL"); + let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_string()) + .expect("Invalid token endpoint URL"); + + BasicClient::new( + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + auth_url, + Some(token_url), + ) + .set_redirect_uri(RedirectUrl::new(redirect_url).unwrap()) +} diff --git a/backend/server/src/service/organisation.rs b/backend/server/src/service/organisation.rs new file mode 100644 index 00000000..31396cdc --- /dev/null +++ b/backend/server/src/service/organisation.rs @@ -0,0 +1,22 @@ +use crate::models::error::ChaosError; +use sqlx::{Pool, Postgres}; + +pub async fn user_is_organisation_admin( + user_id: i64, + organisation_id: i64, + pool: &Pool, +) -> Result<(), ChaosError> { + let is_admin = sqlx::query!( + "SELECT EXISTS(SELECT 1 FROM organisation_members WHERE organisation_id = $1 AND user_id = $2 AND role = 'Admin')", + organisation_id, + user_id + ) + .fetch_one(pool) + .await?.exists.expect("`exists` should always exist in this query result"); + + if !is_admin { + return Err(ChaosError::Unauthorized); + } + + Ok(()) +} diff --git a/backend/server/src/service/role.rs b/backend/server/src/service/role.rs new file mode 100644 index 00000000..52329869 --- /dev/null +++ b/backend/server/src/service/role.rs @@ -0,0 +1,34 @@ +use crate::models::error::ChaosError; +use sqlx::{Pool, Postgres}; + +pub async fn user_is_role_admin( + user_id: i64, + role_id: i64, + pool: &Pool, +) -> Result<(), ChaosError> { + let is_admin = sqlx::query!( + " + SELECT EXISTS( + SELECT 1 FROM ( + SELECT c.organisation_id FROM campaign_roles r + JOIN campaigns c on r.campaign_id = c.id + WHERE r.id = $1 + ) cr + JOIN organisation_members m on cr.organisation_id = m.organisation_id + WHERE m.user_id = $2 AND m.role = 'Admin' + ) + ", + role_id, + user_id + ) + .fetch_one(pool) + .await? + .exists + .expect("`exists` should always exist in this query result"); + + if !is_admin { + return Err(ChaosError::Unauthorized); + } + + Ok(()) +} diff --git a/backend/server/src/src/database/schema.rs b/backend/server/src/src/database/schema.rs deleted file mode 100644 index f138f6d2..00000000 --- a/backend/server/src/src/database/schema.rs +++ /dev/null @@ -1,174 +0,0 @@ -// @generated automatically by Diesel CLI. - -pub mod sql_types { - #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "admin_level"))] - pub struct AdminLevel; - - #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "application_status"))] - pub struct ApplicationStatus; - - #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "user_gender"))] - pub struct UserGender; -} - -diesel::table! { - answers (id) { - id -> Int4, - application_id -> Int4, - question_id -> Int4, - description -> Text, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - use diesel::sql_types::*; - use super::sql_types::ApplicationStatus; - - applications (id) { - id -> Int4, - user_id -> Int4, - role_id -> Int4, - status -> ApplicationStatus, - created_at -> Timestamp, - updated_at -> Timestamp, - private_status -> Nullable, - } -} - -diesel::table! { - campaigns (id) { - id -> Int4, - organisation_id -> Int4, - name -> Text, - cover_image -> Nullable, - description -> Text, - starts_at -> Timestamp, - ends_at -> Timestamp, - published -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - comments (id) { - id -> Int4, - application_id -> Int4, - commenter_user_id -> Int4, - description -> Text, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - use diesel::sql_types::*; - use super::sql_types::AdminLevel; - - organisation_users (id) { - id -> Int4, - user_id -> Int4, - organisation_id -> Int4, - admin_level -> AdminLevel, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - organisations (id) { - id -> Int4, - name -> Text, - logo -> Nullable, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - questions (id) { - id -> Int4, - role_ids -> Array>, - title -> Text, - description -> Nullable, - max_bytes -> Int4, - required -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - ratings (id) { - id -> Int4, - application_id -> Int4, - rater_user_id -> Int4, - rating -> Int4, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - roles (id) { - id -> Int4, - campaign_id -> Int4, - name -> Text, - description -> Nullable, - min_available -> Int4, - max_available -> Int4, - finalised -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - use diesel::sql_types::*; - use super::sql_types::UserGender; - - users (id) { - id -> Int4, - email -> Text, - zid -> Text, - display_name -> Text, - degree_name -> Text, - degree_starting_year -> Int4, - superuser -> Bool, - created_at -> Timestamp, - updated_at -> Timestamp, - gender -> UserGender, - pronouns -> Text, - } -} - -diesel::joinable!(answers -> applications (application_id)); -diesel::joinable!(answers -> questions (question_id)); -diesel::joinable!(applications -> roles (role_id)); -diesel::joinable!(applications -> users (user_id)); -diesel::joinable!(campaigns -> organisations (organisation_id)); -diesel::joinable!(comments -> applications (application_id)); -diesel::joinable!(comments -> users (commenter_user_id)); -diesel::joinable!(organisation_users -> organisations (organisation_id)); -diesel::joinable!(organisation_users -> users (user_id)); -diesel::joinable!(ratings -> applications (application_id)); -diesel::joinable!(ratings -> users (rater_user_id)); -diesel::joinable!(roles -> campaigns (campaign_id)); - -diesel::allow_tables_to_appear_in_same_query!( - answers, - applications, - campaigns, - comments, - organisation_users, - organisations, - questions, - ratings, - roles, - users, -); diff --git a/backend/server/src/state/mod.rs b/backend/server/src/state/mod.rs deleted file mode 100644 index 8a83510a..00000000 --- a/backend/server/src/state/mod.rs +++ /dev/null @@ -1,66 +0,0 @@ -use dotenv; -use jsonwebtoken::{DecodingKey, EncodingKey}; -use once_cell::sync::OnceCell; -use reqwest::Client; -use serde_json::Value; - -const GOOGLE_OPENID_DISCOVERY_URL: &str = - "https://accounts.google.com/.well-known/openid-configuration"; -const GOOGLE_OPENID_USERINFO_KEY: &str = "userinfo_endpoint"; - -pub struct GlobalStringRef {} - -static INSTANCE: OnceCell = OnceCell::new(); - -impl GlobalStringRef { - pub fn global() -> &'static String { - INSTANCE.get().expect("String not initialized") - } -} - -pub struct ApiState { - pub reqwest_client: Client, - pub jwt_encoding_key: EncodingKey, - pub jwt_decoding_key: DecodingKey, - pub userinfo_endpoint: String, -} - -pub async fn api_state() -> ApiState { - let reqwest_client = Client::new(); - - let jwt_secret = dotenv::var("JWT_SECRET").expect("JWT_SECRET should be in env"); - - INSTANCE - .set(jwt_secret) - .expect("Failed to initialize jwt_secret"); - - let discovery = reqwest::get(GOOGLE_OPENID_DISCOVERY_URL) - .await - .expect(&format!( - "Failed to fetch openid discovery from {:?}", - GOOGLE_OPENID_DISCOVERY_URL - )) - .json::>() - .await - .expect("Failed to parse openid discovery as a JSON Object"); - - let userinfo_endpoint = match discovery.get(GOOGLE_OPENID_USERINFO_KEY).expect(&format!( - "Openid discovery does not contain a {:?} key", - GOOGLE_OPENID_USERINFO_KEY - )) { - Value::String(url) => url.to_string(), - other => panic!( - "Openid discovery {:?} key has incorrect type {:?}", - GOOGLE_OPENID_USERINFO_KEY, other - ), - }; - - let api_state = ApiState { - reqwest_client, - jwt_encoding_key: EncodingKey::from_secret(GlobalStringRef::global().as_bytes()), - jwt_decoding_key: DecodingKey::from_secret(GlobalStringRef::global().as_bytes()), - userinfo_endpoint, - }; - - api_state -} diff --git a/backend/server/src/static_resources.rs b/backend/server/src/static_resources.rs deleted file mode 100644 index a1df8a5b..00000000 --- a/backend/server/src/static_resources.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::path::{Path, PathBuf}; - -use rocket::{ - fs::NamedFile, - get, - response::{self, Responder}, - Request, Response, -}; - -pub struct CachedFile(NamedFile); - -impl<'r> Responder<'r, 'r> for CachedFile { - fn respond_to(self, req: &Request) -> response::Result<'r> { - Response::build_from(self.0.respond_to(req)?) - .raw_header("Cache-control", "max-age=86400") // 24h (24*60*60) - .ok() - } -} - -#[get("/images/")] -pub async fn files(file: PathBuf) -> Option { - NamedFile::open(Path::new("images/").join(file)) - .await - .ok() - .map(|nf| CachedFile(nf)) -} diff --git a/backend/server/src/user.rs b/backend/server/src/user.rs deleted file mode 100644 index 21902bfd..00000000 --- a/backend/server/src/user.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::database::{ - models::{Application, Campaign, OrganisationUser, User}, - Database, -}; -use crate::error::JsonErr; -use diesel::PgConnection; -use rocket::{ - get, - http::Status, - serde::{json::Json, Serialize}, -}; - -#[derive(Serialize)] -pub struct UserResponse { - email: String, - zid: String, - display_name: String, - degree_name: String, - degree_starting_year: i32, -} - -#[derive(Serialize)] -pub enum UserError { - UserNotFound, - CampaignNotFound, - PermissionDenied, -} - -fn user_is_boss(boss_user: &User, user: &User, conn: &PgConnection) -> bool { - if boss_user.id == user.id { - return true; - } - - let apps = Application::get_all_from_user_id(conn, user.id); - for app in &apps { - let boss_mode = OrganisationUser::role_admin_level(app.role_id, boss_user.id, conn) - .is_at_least_director() - .check() - .is_ok(); - if boss_mode { - return true; - } - } - false -} - -#[get("/")] -pub async fn get(user: User) -> Json { - Json(UserResponse { - email: user.email, - zid: user.zid, - display_name: user.display_name, - degree_name: user.degree_name, - degree_starting_year: user.degree_starting_year, - }) -} - -#[get("/")] -pub async fn get_user( - user_id: i32, - user: User, - db: Database, -) -> Result, JsonErr> { - db.run(move |conn| { - let res = User::get_from_id(&conn, user_id) - .ok_or(JsonErr(UserError::UserNotFound, Status::NotFound))?; - - if user_is_boss(&user, &res, conn) { - Ok(Json(UserResponse { - email: user.email, - zid: user.zid, - display_name: user.display_name, - degree_name: user.degree_name, - degree_starting_year: user.degree_starting_year, - })) - } else { - Err(JsonErr(UserError::PermissionDenied, Status::Forbidden)) - } - }) - .await -} - -#[get("/campaigns")] -pub async fn get_user_campaigns(user: User, db: Database) -> Json> { - let campaigns = db.run(move |conn| user.get_all_campaigns(conn)).await; - - Json(campaigns) -} diff --git a/backend/setup-dev-env.sh b/backend/setup-dev-env.sh new file mode 100755 index 00000000..0eaa03a0 --- /dev/null +++ b/backend/setup-dev-env.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +# Drop the caller into a new shell that has the required dependencies, namely +# postgres and sqlx, installed and running. This is required because the Rust +# backend can only be built and run if the database is also running, due to +# sqlx. + +# Write .env file to temporary file. +tmp_env_file="$(mktemp)" +trap 'rm -rf "$tmp_env_file"' EXIT INT TERM +cat << 'EOF' > "$tmp_env_file" +DATABASE_URL="postgres://user:password@localhost:5432/chaos" +JWT_SECRET="test_secret" +GOOGLE_CLIENT_ID="test" +GOOGLE_CLIENT_SECRET="test" +GOOGLE_REDIRECT_URI="http://localhost:3000/auth/callback" +S3_BUCKET_NAME="chaos-storage" +S3_ACCESS_KEY="test_access_key" +S3_SECRET_KEY="test_secret_key" +S3_ENDPOINT="https://chaos-storage.s3.ap-southeast-1.amazonaws.com" +S3_REGION_NAME="ap-southeast-1" +EOF + +# Check the user has all required tools installed. +for cmd in "which cargo" "which docker && docker info" "which docker-compose || docker compose"; do + if ! eval "$cmd" 1>/dev/null 2>&1; then + echo "The command '$cmd' failed, indicating you might not have that tool installed." >&2 + exit 1 + fi +done + +# Create .env file. +env_file=.env + +# The .env file already exists. +if [ -f "$env_file" ]; then + # If existing env file differs from new one, save the existing env file to a backup file. + if ! diff "$env_file" "$tmp_env_file" > /dev/null; then + backup_env_file="$env_file" + while [ -f "$backup_env_file" ]; do + # Append `.backup` to backup filename until we find a filename that doesn't exist yet. + backup_env_file="$backup_env_file.backup" + done + + echo "Saving existing env file '$env_file' to backup env file '$backup_env_file'" + mv "$env_file" "$backup_env_file" + fi +fi + +echo "Overwriting $env_file file" +cp "$tmp_env_file" "$env_file" + +# Install sqlx if it isn't installed yet. +if ! which sqlx >/dev/null; then + echo "Installing sqlx" + cargo install sqlx-cli --no-default-features --features native-tls,postgres +else + echo "sqlx already installed" +fi + +# Run postgres database in the background. +this_script_dir="$(dirname "$(realpath "$0")")" +docker_compose_file_name="setup-test-database.yml" +docker_compose_file_path="$this_script_dir/$docker_compose_file_name" + +echo 'Starting up postgres database in docker' +docker-compose -f "$docker_compose_file_path" up --detach || exit 1 + +# Ensure the docker container gets killed once this script exits. +trap 'echo "shutting down $docker_compose_file_path" && docker-compose -f "$docker_compose_file_path" down' EXIT INT TERM + +# Wait for the database to be ready. +echo "Waiting for database to be ready" +sleep 3 + +# Setup sqlx. +echo "Setting up sqlx" +sqlx database create || exit 1 +sqlx migrate run || exit 1 + +"$SHELL" diff --git a/backend/setup-test-database.yml b/backend/setup-test-database.yml new file mode 100644 index 00000000..d30d0b4e --- /dev/null +++ b/backend/setup-test-database.yml @@ -0,0 +1,17 @@ +# Run `docker-compose -f setup-test-database.yml up` + +services: + database: + # Official Postgres image from DockerHub. + image: 'postgres:16' + + # By default, a Postgres database is running on port 5432, so make that port accessible from outside of the container. + ports: + - 5432:5432 + + environment: + # Username and password to access database. + POSTGRES_USER: user + POSTGRES_PASSWORD: password + # Name of the database. + POSTGRES_DB: chaos diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a3cf6988..623ffd66 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,15 +15,19 @@ "@headlessui/tailwindcss": "^0.1.3", "@hello-pangea/dnd": "16.2.0", "@heroicons/react": "2.0.13", + "@lukemorales/query-key-factory": "^1.3.4", "@mui/icons-material": "5.11.16", "@mui/lab": "5.0.0-alpha.129", "@mui/material": "5.13.0", "@mui/system": "5.13.2", "@mui/x-date-pickers": "6.7.0", "@stitches/react": "1.2.8", + "@tanstack/react-query": "^5.51.21", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", + "assert": "^2.1.0", + "axios": "^1.7.3", "bezier-easing": "2.1.0", "date-fns": "2.30.0", "http-proxy-middleware": "2.0.6", @@ -44,6 +48,7 @@ "@babel/preset-react": "7.22.3", "@tailwindcss/aspect-ratio": "0.4.2", "@tailwindcss/forms": "0.5.3", + "@types/assert": "^1.5.10", "@types/node": "18.16.16", "@types/react": "18.2.8", "@types/react-dom": "18.2.4", @@ -67,13 +72,14 @@ "tailwindcss": "3.3.2", "tailwindcss-gradient": "1.0.1", "twin.macro": "3.3.1", - "vite": "4.3.5", + "vite": "^4.5.3", "vite-tsconfig-paths": "4.2.0" } }, "node_modules/@adobe/css-tools": { - "version": "4.0.1", - "license": "MIT" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -102,12 +108,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", - "license": "MIT", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -174,15 +180,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz", - "integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -489,19 +494,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", - "license": "MIT", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "license": "MIT", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } @@ -548,25 +551,27 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "license": "MIT", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", - "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1985,36 +1990,31 @@ } }, "node_modules/@babel/template": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", - "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/parser": "^7.21.9", - "@babel/types": "^7.21.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz", - "integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.22.3", - "@babel/helper-environment-visitor": "^7.22.1", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.22.4", - "@babel/types": "^7.22.4", - "debug": "^4.1.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2022,13 +2022,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", - "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", - "license": "MIT", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2204,13 +2203,254 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", "license": "MIT" }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.18", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -2219,6 +2459,102 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2534,14 +2870,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "license": "MIT", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2557,10 +2892,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "license": "MIT", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -2572,11 +2906,24 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "license": "MIT", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukemorales/query-key-factory": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lukemorales/query-key-factory/-/query-key-factory-1.3.4.tgz", + "integrity": "sha512-A3frRDdkmaNNQi6mxIshsDk4chRXWoXa05US8fBo4kci/H+lVmujS6QrwQLLGIkNIRFGjMqp2uKjC4XsLdydRw==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@tanstack/query-core": ">= 4.0.0", + "@tanstack/react-query": ">= 4.0.0" } }, "node_modules/@mui/base": { @@ -3103,6 +3450,8 @@ }, "node_modules/@popperjs/core": { "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", "funding": { "type": "opencollective", @@ -3154,6 +3503,30 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" } }, + "node_modules/@tanstack/query-core": { + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz", + "integrity": "sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.21.tgz", + "integrity": "sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw==", + "dependencies": { + "@tanstack/query-core": "5.51.21" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.1", "license": "MIT", @@ -3367,6 +3740,12 @@ "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", "license": "MIT" }, + "node_modules/@types/assert": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.5.10.tgz", + "integrity": "sha512-qEO+AUgYab7GVbeDDgUNCU3o0aZUoIMpNAe+w5LDbRxfxQX7vQAdDgwj1AroX+i8KaV56FWg0srXlSZROnsrIQ==", + "dev": true + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -3586,26 +3965,11 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3613,13 +3977,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/@typescript-eslint/parser": { "version": "5.59.8", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.8.tgz", @@ -3736,26 +4093,11 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3763,13 +4105,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/@typescript-eslint/utils": { "version": "5.59.8", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.8.tgz", @@ -3797,26 +4132,11 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3824,13 +4144,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.59.8", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz", @@ -3927,7 +4240,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -4058,6 +4370,18 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -4065,6 +4389,11 @@ "dev": true, "license": "ISC" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -4130,6 +4459,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -4229,12 +4568,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "license": "MIT", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4323,7 +4661,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4337,7 +4674,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -4406,7 +4742,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -4414,8 +4749,18 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } }, "node_modules/commander": { "version": "4.1.1", @@ -4617,6 +4962,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4793,10 +5146,11 @@ } }, "node_modules/esbuild": { - "version": "0.17.18", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -4804,28 +5158,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.18", - "@esbuild/android-arm64": "0.17.18", - "@esbuild/android-x64": "0.17.18", - "@esbuild/darwin-arm64": "0.17.18", - "@esbuild/darwin-x64": "0.17.18", - "@esbuild/freebsd-arm64": "0.17.18", - "@esbuild/freebsd-x64": "0.17.18", - "@esbuild/linux-arm": "0.17.18", - "@esbuild/linux-arm64": "0.17.18", - "@esbuild/linux-ia32": "0.17.18", - "@esbuild/linux-loong64": "0.17.18", - "@esbuild/linux-mips64el": "0.17.18", - "@esbuild/linux-ppc64": "0.17.18", - "@esbuild/linux-riscv64": "0.17.18", - "@esbuild/linux-s390x": "0.17.18", - "@esbuild/linux-x64": "0.17.18", - "@esbuild/netbsd-x64": "0.17.18", - "@esbuild/openbsd-x64": "0.17.18", - "@esbuild/sunos-x64": "0.17.18", - "@esbuild/win32-arm64": "0.17.18", - "@esbuild/win32-ia32": "0.17.18", - "@esbuild/win32-x64": "0.17.18" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escalade": { @@ -5607,10 +5961,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "license": "MIT", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5663,16 +6016,15 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5691,6 +6043,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -5711,6 +6076,19 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5932,7 +6310,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", "engines": { "node": ">=4" } @@ -6243,6 +6620,20 @@ "node": ">=0.10.0" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6264,6 +6655,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -6281,7 +6687,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -7125,6 +7530,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7192,16 +7616,15 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7563,10 +7986,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "license": "ISC" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -7599,7 +8021,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -7614,11 +8038,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7824,6 +8247,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.1.1", "dev": true, @@ -8208,9 +8636,10 @@ } }, "node_modules/rollup": { - "version": "3.21.6", + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "dev": true, - "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -8270,11 +8699,10 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -8335,10 +8763,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "license": "BSD-3-Clause", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -8514,7 +8941,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -8665,7 +9091,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -8712,9 +9137,10 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -9004,6 +9430,18 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9011,15 +9449,14 @@ "license": "MIT" }, "node_modules/vite": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz", - "integrity": "sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, - "license": "MIT", "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -9027,12 +9464,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -9045,6 +9486,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -9151,11 +9595,10 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } diff --git a/frontend/package.json b/frontend/package.json index 858b5a3a..220b15e3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,15 +10,19 @@ "@headlessui/tailwindcss": "^0.1.3", "@hello-pangea/dnd": "16.2.0", "@heroicons/react": "2.0.13", + "@lukemorales/query-key-factory": "^1.3.4", "@mui/icons-material": "5.11.16", "@mui/lab": "5.0.0-alpha.129", "@mui/material": "5.13.0", "@mui/system": "5.13.2", "@mui/x-date-pickers": "6.7.0", "@stitches/react": "1.2.8", + "@tanstack/react-query": "^5.51.21", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", + "assert": "^2.1.0", + "axios": "^1.7.3", "bezier-easing": "2.1.0", "date-fns": "2.30.0", "http-proxy-middleware": "2.0.6", @@ -64,6 +68,7 @@ "@babel/preset-react": "7.22.3", "@tailwindcss/aspect-ratio": "0.4.2", "@tailwindcss/forms": "0.5.3", + "@types/assert": "^1.5.10", "@types/node": "18.16.16", "@types/react": "18.2.8", "@types/react-dom": "18.2.4", @@ -87,7 +92,7 @@ "tailwindcss": "3.3.2", "tailwindcss-gradient": "1.0.1", "twin.macro": "3.3.1", - "vite": "4.3.5", + "vite": "^4.5.3", "vite-tsconfig-paths": "4.2.0" }, "husky": { diff --git a/frontend/src/api/axios.ts b/frontend/src/api/axios.ts new file mode 100644 index 00000000..fc8f8210 --- /dev/null +++ b/frontend/src/api/axios.ts @@ -0,0 +1,31 @@ +import assert from "assert"; + +import Axios from "axios"; + +assert( + typeof import.meta.env.VITE_API_BASE_URL === "string", + "VITE_API_BASE_URL is required" +); + +// global axios instance +const axios = Axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, +}); + +type Params = { + path: string; // relative path to the base url + data: ReqType; // request body + method: "get" | "post" | "put" | "delete" | "patch"; +}; + +const request = async ({ + path, + data, + method, +}: Params): Promise => { + const { data: res } = await axios(path, { data, method }); + + return res; +}; + +export default request; diff --git a/frontend/src/api/organisation/index.ts b/frontend/src/api/organisation/index.ts new file mode 100644 index 00000000..5b148bf2 --- /dev/null +++ b/frontend/src/api/organisation/index.ts @@ -0,0 +1,27 @@ +import { createQueryKeys } from "@lukemorales/query-key-factory"; + +import request from "api/axios"; + +type OrganisationCreateRequest = { + name: string; + admin: number; +}; +type OrganisationCreateResponse = { + message: string; +}; + +const createOrganisation = (data: OrganisationCreateRequest) => + request({ + path: "/organisation", + data, + method: "post", + }); + +const organisation = createQueryKeys("organisation", { + create: (data: OrganisationCreateRequest) => ({ + queryKey: [data.name, data.admin], + queryFn: () => createOrganisation(data), + }), +}); + +export default organisation; diff --git a/frontend/src/api/query-client.ts b/frontend/src/api/query-client.ts new file mode 100644 index 00000000..4aa69abd --- /dev/null +++ b/frontend/src/api/query-client.ts @@ -0,0 +1,9 @@ +import { QueryClient } from "@tanstack/react-query"; + +const defaultQueryConfig = {} as const; + +const queryClient = new QueryClient({ + defaultOptions: { queries: defaultQueryConfig }, +}); + +export default queryClient; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 907a07a6..0f09e87a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3,7 +3,9 @@ "@adobe/css-tools@^4.0.1": - version "4.0.1" + version "4.4.0" + resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz" + integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== "@alloc/quick-lru@^5.2.0": version "5.2.0" @@ -18,12 +20,13 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4": - version "7.21.4" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3": version "7.22.3" @@ -60,14 +63,14 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.22.0", "@babel/generator@^7.22.3": - version "7.22.3" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz" - integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== +"@babel/generator@^7.22.0", "@babel/generator@^7.25.0": + version "7.25.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== dependencies: - "@babel/types" "^7.22.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" + "@babel/types" "^7.25.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.18.6": @@ -240,15 +243,15 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.21.5": - version "7.21.5" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz" - integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== "@babel/helper-validator-option@^7.21.0": version "7.21.0" @@ -274,19 +277,22 @@ "@babel/traverse" "^7.22.1" "@babel/types" "^7.22.3" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": - version "7.22.4" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz" - integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== +"@babel/parser@^7.22.0", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3": + version "7.25.3" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz" + integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== + dependencies: + "@babel/types" "^7.25.2" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -1010,38 +1016,35 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9": - version "7.21.9" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz" - integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9", "@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/parser" "^7.21.9" - "@babel/types" "^7.21.5" + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" "@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1": - version "7.22.4" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz" - integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.3" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.22.4" - "@babel/types" "^7.22.4" - debug "^4.1.0" + version "7.25.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz" + integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.2" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.4.4": - version "7.22.4" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz" - integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA== +"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.4.4": + version "7.25.2" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== dependencies: - "@babel/helper-string-parser" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" "@date-io/core@^2.16.0": @@ -1163,8 +1166,10 @@ resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@esbuild/linux-x64@0.17.18": - version "0.17.18" +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1286,35 +1291,42 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0": +"@jridgewell/resolve-uri@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.14" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@lukemorales/query-key-factory@^1.3.4": + version "1.3.4" + resolved "https://registry.npmjs.org/@lukemorales/query-key-factory/-/query-key-factory-1.3.4.tgz" + integrity sha512-A3frRDdkmaNNQi6mxIshsDk4chRXWoXa05US8fBo4kci/H+lVmujS6QrwQLLGIkNIRFGjMqp2uKjC4XsLdydRw== "@mui/base@^5.0.0-alpha.87": version "5.0.0-beta.13" @@ -1500,6 +1512,8 @@ "@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8": version "2.11.8" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@remix-run/router@1.6.3": version "1.6.3" @@ -1526,6 +1540,18 @@ dependencies: mini-svg-data-uri "^1.2.3" +"@tanstack/query-core@>= 4.0.0", "@tanstack/query-core@5.51.21": + version "5.51.21" + resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz" + integrity sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw== + +"@tanstack/react-query@^5.51.21", "@tanstack/react-query@>= 4.0.0": + version "5.51.21" + resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.21.tgz" + integrity sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw== + dependencies: + "@tanstack/query-core" "5.51.21" + "@testing-library/dom@^9.0.0", "@testing-library/dom@>=7.21.4": version "9.3.1" dependencies: @@ -1572,6 +1598,11 @@ resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz" integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== +"@types/assert@^1.5.10": + version "1.5.10" + resolved "https://registry.npmjs.org/@types/assert/-/assert-1.5.10.tgz" + integrity sha512-qEO+AUgYab7GVbeDDgUNCU3o0aZUoIMpNAe+w5LDbRxfxQX7vQAdDgwj1AroX+i8KaV56FWg0srXlSZROnsrIQ== + "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz" @@ -1909,11 +1940,27 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" +assert@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz" integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + attr-accept@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz" @@ -1941,6 +1988,15 @@ axe-core@^4.6.2: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz" integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== +axios@^1.7.3: + version "1.7.3" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz" + integrity sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz" @@ -2005,11 +2061,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browserslist@^4.21.3, browserslist@^4.21.5, "browserslist@>= 4.21.0": version "4.21.5" @@ -2042,7 +2098,7 @@ camelcase-css@^2.0.1: caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: version "1.0.30001486" -chalk@^2.0.0: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2143,6 +2199,13 @@ color-name@1.1.3: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" @@ -2231,7 +2294,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2271,6 +2334,11 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" @@ -2394,31 +2462,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.17.5: - version "0.17.18" +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== optionalDependencies: - "@esbuild/android-arm" "0.17.18" - "@esbuild/android-arm64" "0.17.18" - "@esbuild/android-x64" "0.17.18" - "@esbuild/darwin-arm64" "0.17.18" - "@esbuild/darwin-x64" "0.17.18" - "@esbuild/freebsd-arm64" "0.17.18" - "@esbuild/freebsd-x64" "0.17.18" - "@esbuild/linux-arm" "0.17.18" - "@esbuild/linux-arm64" "0.17.18" - "@esbuild/linux-ia32" "0.17.18" - "@esbuild/linux-loong64" "0.17.18" - "@esbuild/linux-mips64el" "0.17.18" - "@esbuild/linux-ppc64" "0.17.18" - "@esbuild/linux-riscv64" "0.17.18" - "@esbuild/linux-s390x" "0.17.18" - "@esbuild/linux-x64" "0.17.18" - "@esbuild/netbsd-x64" "0.17.18" - "@esbuild/openbsd-x64" "0.17.18" - "@esbuild/sunos-x64" "0.17.18" - "@esbuild/win32-arm64" "0.17.18" - "@esbuild/win32-ia32" "0.17.18" - "@esbuild/win32-x64" "0.17.18" + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" escalade@^3.1.1: version "3.1.1" @@ -2752,10 +2822,10 @@ file-selector@^0.6.0: dependencies: tslib "^2.4.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -2785,10 +2855,10 @@ flatted@^3.1.0: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.0.0, follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" @@ -2797,6 +2867,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz" @@ -2807,6 +2886,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -3063,7 +3147,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@^2.0.3, inherits@2: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3075,7 +3159,7 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -is-arguments@^1.1.0, is-arguments@^1.1.1: +is-arguments@^1.0.4, is-arguments@^1.1.0, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -3139,6 +3223,13 @@ is-extglob@^2.1.1: resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -3151,6 +3242,14 @@ is-map@^2.0.1, is-map@^2.0.2: resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" @@ -3212,7 +3311,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10: +is-typed-array@^1.1.10, is-typed-array@^1.1.3: version "1.1.10" resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -3345,7 +3444,9 @@ json-stable-stringify-without-jsonify@^1.0.1: integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^1.0.1: - version "1.0.1" + version "1.0.2" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -3433,13 +3534,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - lz-string@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" @@ -3463,6 +3557,18 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" @@ -3502,10 +3608,10 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare-lite@^1.4.0: version "1.4.0" @@ -3693,10 +3799,10 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" @@ -3757,12 +3863,14 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@>=8.0.9: - version "8.4.29" +postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.27, postcss@>=8.0.9: + version "8.4.41" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" prelude-ls@^1.2.1: version "1.2.1" @@ -3813,6 +3921,11 @@ prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.1.1" @@ -4034,8 +4147,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup@^3.21.0: - version "3.21.6" +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== optionalDependencies: fsevents "~2.3.2" @@ -4063,14 +4178,14 @@ scheduler@^0.23.0: loose-envify "^1.1.0" semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.7: - version "7.3.8" - dependencies: - lru-cache "^6.0.0" + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== shebang-command@^2.0.0: version "2.0.0" @@ -4103,10 +4218,10 @@ slash@^4.0.0: resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== source-map@^0.5.7: version "0.5.7" @@ -4427,6 +4542,17 @@ util-deprecate@^1.0.2: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.12.5: + version "0.12.5" + resolved "https://registry.npmjs.org/util/-/util-0.12.5.tgz" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + vite-tsconfig-paths@4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.0.tgz" @@ -4436,14 +4562,14 @@ vite-tsconfig-paths@4.2.0: globrex "^0.1.2" tsconfck "^2.1.0" -vite@*, vite@^4.2.0, vite@4.3.5: - version "4.3.5" - resolved "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz" - integrity sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA== +vite@*, vite@^4.2.0, vite@^4.5.3: + version "4.5.3" + resolved "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz" + integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== dependencies: - esbuild "^0.17.5" - postcss "^8.4.23" - rollup "^3.21.0" + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" optionalDependencies: fsevents "~2.3.2" @@ -4473,7 +4599,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.8: +which-typed-array@^1.1.2, which-typed-array@^1.1.8: version "1.1.9" dependencies: available-typed-arrays "^1.0.5" @@ -4491,9 +4617,9 @@ which@^2.0.1: isexe "^2.0.0" word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrappy@1: version "1.0.2" @@ -4505,11 +4631,6 @@ yallist@^3.0.2: resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"