From a73e7deeff7cb5ee1ae3966fa93ea6839244f1d8 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 28 May 2026 22:28:59 +0200 Subject: [PATCH] remove legacy v1 backend --- thoughts-backend/.dockerignore | 6 - thoughts-backend/.env | 8 - thoughts-backend/.env.example | 6 - thoughts-backend/.gitignore | 2 - thoughts-backend/Cargo.lock | 5093 ----------------- thoughts-backend/Cargo.toml | 62 - thoughts-backend/Dockerfile | 44 - thoughts-backend/LICENSE | 21 - thoughts-backend/README.md | 17 - thoughts-backend/api/Cargo.toml | 42 - thoughts-backend/api/src/error/adapter.rs | 41 - thoughts-backend/api/src/error/core.rs | 10 - thoughts-backend/api/src/error/handler.rs | 36 - thoughts-backend/api/src/error/mod.rs | 7 - thoughts-backend/api/src/error/traits.rs | 5 - thoughts-backend/api/src/extractor/auth.rs | 76 - thoughts-backend/api/src/extractor/json.rs | 26 - thoughts-backend/api/src/extractor/mod.rs | 10 - .../api/src/extractor/optional_auth.rs | 21 - thoughts-backend/api/src/extractor/valid.rs | 23 - thoughts-backend/api/src/init.rs | 34 - thoughts-backend/api/src/lib.rs | 9 - thoughts-backend/api/src/models/mod.rs | 3 - thoughts-backend/api/src/models/response.rs | 27 - thoughts-backend/api/src/routers/api_key.rs | 93 - thoughts-backend/api/src/routers/auth.rs | 93 - thoughts-backend/api/src/routers/feed.rs | 67 - thoughts-backend/api/src/routers/friends.rs | 24 - thoughts-backend/api/src/routers/mod.rs | 35 - thoughts-backend/api/src/routers/root.rs | 36 - thoughts-backend/api/src/routers/search.rs | 53 - thoughts-backend/api/src/routers/tag.rs | 51 - thoughts-backend/api/src/routers/thought.rs | 145 - thoughts-backend/api/src/routers/user.rs | 476 -- thoughts-backend/api/src/validation/mod.rs | 3 - .../api/src/validation/rejection.rs | 58 - thoughts-backend/app/Cargo.toml | 17 - thoughts-backend/app/README.md | 3 - thoughts-backend/app/src/config.rs | 28 - thoughts-backend/app/src/error/mod.rs | 3 - thoughts-backend/app/src/error/user.rs | 26 - thoughts-backend/app/src/lib.rs | 4 - .../app/src/persistence/api_key.rs | 93 - thoughts-backend/app/src/persistence/auth.rs | 55 - .../app/src/persistence/follow.rs | 91 - thoughts-backend/app/src/persistence/mod.rs | 7 - .../app/src/persistence/search.rs | 66 - thoughts-backend/app/src/persistence/tag.rs | 120 - .../app/src/persistence/thought.rs | 386 -- thoughts-backend/app/src/persistence/user.rs | 186 - thoughts-backend/app/src/state.rs | 7 - thoughts-backend/common/Cargo.toml | 14 - thoughts-backend/common/src/lib.rs | 53 - thoughts-backend/doc/Cargo.toml | 24 - thoughts-backend/doc/src/api_key.rs | 16 - thoughts-backend/doc/src/auth.rs | 23 - thoughts-backend/doc/src/feed.rs | 10 - thoughts-backend/doc/src/friends.rs | 12 - thoughts-backend/doc/src/lib.rs | 51 - thoughts-backend/doc/src/root.rs | 7 - thoughts-backend/doc/src/search.rs | 21 - thoughts-backend/doc/src/tag.rs | 12 - thoughts-backend/doc/src/thought.rs | 22 - thoughts-backend/doc/src/user.rs | 37 - thoughts-backend/migration/Cargo.toml | 15 - thoughts-backend/migration/README.md | 59 - thoughts-backend/migration/src/lib.rs | 28 - .../migration/src/m20240101_000001_init.rs | 47 - .../migration/src/m20250905_000001_init.rs | 101 - .../m20250906_100000_add_profile_fields.rs | 107 - .../src/m20250906_130237_add_tags.rs | 74 - .../src/m20250906_134056_add_api_keys.rs | 69 - .../m20250906_145148_add_reply_to_thoughts.rs | 46 - ...50906_145755_add_visibility_to_thoughts.rs | 59 - .../m20250906_231359_add_full_text_search.rs | 48 - thoughts-backend/migration/src/main.rs | 6 - thoughts-backend/models/Cargo.toml | 23 - thoughts-backend/models/README.md | 13 - .../models/src/domains/api_key.rs | 32 - thoughts-backend/models/src/domains/follow.rs | 38 - thoughts-backend/models/src/domains/mod.rs | 11 - .../models/src/domains/prelude.rs | 9 - thoughts-backend/models/src/domains/tag.rs | 27 - .../models/src/domains/thought.rs | 62 - .../models/src/domains/thought_tag.rs | 40 - .../models/src/domains/top_friends.rs | 35 - thoughts-backend/models/src/domains/user.rs | 38 - thoughts-backend/models/src/lib.rs | 4 - thoughts-backend/models/src/params/auth.rs | 21 - thoughts-backend/models/src/params/mod.rs | 3 - thoughts-backend/models/src/params/thought.rs | 19 - thoughts-backend/models/src/params/user.rs | 38 - thoughts-backend/models/src/queries/mod.rs | 2 - .../models/src/queries/pagination.rs | 27 - thoughts-backend/models/src/queries/user.rs | 9 - .../models/src/schemas/api_key.rs | 64 - thoughts-backend/models/src/schemas/mod.rs | 5 - .../models/src/schemas/pagination.rs | 12 - thoughts-backend/models/src/schemas/search.rs | 9 - .../models/src/schemas/thought.rs | 88 - thoughts-backend/models/src/schemas/user.rs | 80 - thoughts-backend/src/main.rs | 27 - thoughts-backend/src/shuttle.rs | 13 - thoughts-backend/src/tokio.rs | 55 - thoughts-backend/tests/api/api_key.rs | 85 - thoughts-backend/tests/api/auth.rs | 62 - thoughts-backend/tests/api/feed.rs | 273 - thoughts-backend/tests/api/follow.rs | 162 - thoughts-backend/tests/api/main.rs | 59 - thoughts-backend/tests/api/mod.rs | 9 - thoughts-backend/tests/api/search.rs | 198 - thoughts-backend/tests/api/tag.rs | 91 - thoughts-backend/tests/api/thought.rs | 321 -- thoughts-backend/tests/api/user.rs | 312 - thoughts-backend/tests/app/mod.rs | 1 - thoughts-backend/tests/app/persistence/mod.rs | 20 - .../tests/app/persistence/user.rs | 18 - thoughts-backend/tests/mod.rs | 2 - thoughts-backend/utils/Cargo.toml | 20 - thoughts-backend/utils/README.md | 3 - thoughts-backend/utils/src/db.rs | 8 - thoughts-backend/utils/src/lib.rs | 4 - thoughts-backend/utils/src/testing/api/mod.rs | 98 - thoughts-backend/utils/src/testing/db/mod.rs | 27 - thoughts-backend/utils/src/testing/mod.rs | 8 - 125 files changed, 11401 deletions(-) delete mode 100644 thoughts-backend/.dockerignore delete mode 100644 thoughts-backend/.env delete mode 100644 thoughts-backend/.env.example delete mode 100644 thoughts-backend/.gitignore delete mode 100644 thoughts-backend/Cargo.lock delete mode 100644 thoughts-backend/Cargo.toml delete mode 100644 thoughts-backend/Dockerfile delete mode 100644 thoughts-backend/LICENSE delete mode 100644 thoughts-backend/README.md delete mode 100644 thoughts-backend/api/Cargo.toml delete mode 100644 thoughts-backend/api/src/error/adapter.rs delete mode 100644 thoughts-backend/api/src/error/core.rs delete mode 100644 thoughts-backend/api/src/error/handler.rs delete mode 100644 thoughts-backend/api/src/error/mod.rs delete mode 100644 thoughts-backend/api/src/error/traits.rs delete mode 100644 thoughts-backend/api/src/extractor/auth.rs delete mode 100644 thoughts-backend/api/src/extractor/json.rs delete mode 100644 thoughts-backend/api/src/extractor/mod.rs delete mode 100644 thoughts-backend/api/src/extractor/optional_auth.rs delete mode 100644 thoughts-backend/api/src/extractor/valid.rs delete mode 100644 thoughts-backend/api/src/init.rs delete mode 100644 thoughts-backend/api/src/lib.rs delete mode 100644 thoughts-backend/api/src/models/mod.rs delete mode 100644 thoughts-backend/api/src/models/response.rs delete mode 100644 thoughts-backend/api/src/routers/api_key.rs delete mode 100644 thoughts-backend/api/src/routers/auth.rs delete mode 100644 thoughts-backend/api/src/routers/feed.rs delete mode 100644 thoughts-backend/api/src/routers/friends.rs delete mode 100644 thoughts-backend/api/src/routers/mod.rs delete mode 100644 thoughts-backend/api/src/routers/root.rs delete mode 100644 thoughts-backend/api/src/routers/search.rs delete mode 100644 thoughts-backend/api/src/routers/tag.rs delete mode 100644 thoughts-backend/api/src/routers/thought.rs delete mode 100644 thoughts-backend/api/src/routers/user.rs delete mode 100644 thoughts-backend/api/src/validation/mod.rs delete mode 100644 thoughts-backend/api/src/validation/rejection.rs delete mode 100644 thoughts-backend/app/Cargo.toml delete mode 100644 thoughts-backend/app/README.md delete mode 100644 thoughts-backend/app/src/config.rs delete mode 100644 thoughts-backend/app/src/error/mod.rs delete mode 100644 thoughts-backend/app/src/error/user.rs delete mode 100644 thoughts-backend/app/src/lib.rs delete mode 100644 thoughts-backend/app/src/persistence/api_key.rs delete mode 100644 thoughts-backend/app/src/persistence/auth.rs delete mode 100644 thoughts-backend/app/src/persistence/follow.rs delete mode 100644 thoughts-backend/app/src/persistence/mod.rs delete mode 100644 thoughts-backend/app/src/persistence/search.rs delete mode 100644 thoughts-backend/app/src/persistence/tag.rs delete mode 100644 thoughts-backend/app/src/persistence/thought.rs delete mode 100644 thoughts-backend/app/src/persistence/user.rs delete mode 100644 thoughts-backend/app/src/state.rs delete mode 100644 thoughts-backend/common/Cargo.toml delete mode 100644 thoughts-backend/common/src/lib.rs delete mode 100644 thoughts-backend/doc/Cargo.toml delete mode 100644 thoughts-backend/doc/src/api_key.rs delete mode 100644 thoughts-backend/doc/src/auth.rs delete mode 100644 thoughts-backend/doc/src/feed.rs delete mode 100644 thoughts-backend/doc/src/friends.rs delete mode 100644 thoughts-backend/doc/src/lib.rs delete mode 100644 thoughts-backend/doc/src/root.rs delete mode 100644 thoughts-backend/doc/src/search.rs delete mode 100644 thoughts-backend/doc/src/tag.rs delete mode 100644 thoughts-backend/doc/src/thought.rs delete mode 100644 thoughts-backend/doc/src/user.rs delete mode 100644 thoughts-backend/migration/Cargo.toml delete mode 100644 thoughts-backend/migration/README.md delete mode 100644 thoughts-backend/migration/src/lib.rs delete mode 100644 thoughts-backend/migration/src/m20240101_000001_init.rs delete mode 100644 thoughts-backend/migration/src/m20250905_000001_init.rs delete mode 100644 thoughts-backend/migration/src/m20250906_100000_add_profile_fields.rs delete mode 100644 thoughts-backend/migration/src/m20250906_130237_add_tags.rs delete mode 100644 thoughts-backend/migration/src/m20250906_134056_add_api_keys.rs delete mode 100644 thoughts-backend/migration/src/m20250906_145148_add_reply_to_thoughts.rs delete mode 100644 thoughts-backend/migration/src/m20250906_145755_add_visibility_to_thoughts.rs delete mode 100644 thoughts-backend/migration/src/m20250906_231359_add_full_text_search.rs delete mode 100644 thoughts-backend/migration/src/main.rs delete mode 100644 thoughts-backend/models/Cargo.toml delete mode 100644 thoughts-backend/models/README.md delete mode 100644 thoughts-backend/models/src/domains/api_key.rs delete mode 100644 thoughts-backend/models/src/domains/follow.rs delete mode 100644 thoughts-backend/models/src/domains/mod.rs delete mode 100644 thoughts-backend/models/src/domains/prelude.rs delete mode 100644 thoughts-backend/models/src/domains/tag.rs delete mode 100644 thoughts-backend/models/src/domains/thought.rs delete mode 100644 thoughts-backend/models/src/domains/thought_tag.rs delete mode 100644 thoughts-backend/models/src/domains/top_friends.rs delete mode 100644 thoughts-backend/models/src/domains/user.rs delete mode 100644 thoughts-backend/models/src/lib.rs delete mode 100644 thoughts-backend/models/src/params/auth.rs delete mode 100644 thoughts-backend/models/src/params/mod.rs delete mode 100644 thoughts-backend/models/src/params/thought.rs delete mode 100644 thoughts-backend/models/src/params/user.rs delete mode 100644 thoughts-backend/models/src/queries/mod.rs delete mode 100644 thoughts-backend/models/src/queries/pagination.rs delete mode 100644 thoughts-backend/models/src/queries/user.rs delete mode 100644 thoughts-backend/models/src/schemas/api_key.rs delete mode 100644 thoughts-backend/models/src/schemas/mod.rs delete mode 100644 thoughts-backend/models/src/schemas/pagination.rs delete mode 100644 thoughts-backend/models/src/schemas/search.rs delete mode 100644 thoughts-backend/models/src/schemas/thought.rs delete mode 100644 thoughts-backend/models/src/schemas/user.rs delete mode 100644 thoughts-backend/src/main.rs delete mode 100644 thoughts-backend/src/shuttle.rs delete mode 100644 thoughts-backend/src/tokio.rs delete mode 100644 thoughts-backend/tests/api/api_key.rs delete mode 100644 thoughts-backend/tests/api/auth.rs delete mode 100644 thoughts-backend/tests/api/feed.rs delete mode 100644 thoughts-backend/tests/api/follow.rs delete mode 100644 thoughts-backend/tests/api/main.rs delete mode 100644 thoughts-backend/tests/api/mod.rs delete mode 100644 thoughts-backend/tests/api/search.rs delete mode 100644 thoughts-backend/tests/api/tag.rs delete mode 100644 thoughts-backend/tests/api/thought.rs delete mode 100644 thoughts-backend/tests/api/user.rs delete mode 100644 thoughts-backend/tests/app/mod.rs delete mode 100644 thoughts-backend/tests/app/persistence/mod.rs delete mode 100644 thoughts-backend/tests/app/persistence/user.rs delete mode 100644 thoughts-backend/tests/mod.rs delete mode 100644 thoughts-backend/utils/Cargo.toml delete mode 100644 thoughts-backend/utils/README.md delete mode 100644 thoughts-backend/utils/src/db.rs delete mode 100644 thoughts-backend/utils/src/lib.rs delete mode 100644 thoughts-backend/utils/src/testing/api/mod.rs delete mode 100644 thoughts-backend/utils/src/testing/db/mod.rs delete mode 100644 thoughts-backend/utils/src/testing/mod.rs diff --git a/thoughts-backend/.dockerignore b/thoughts-backend/.dockerignore deleted file mode 100644 index d680a5b..0000000 --- a/thoughts-backend/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -# Ignore build artifacts -target/ -# Ignore git directory -.git/ -# Ignore local environment files -.env diff --git a/thoughts-backend/.env b/thoughts-backend/.env deleted file mode 100644 index 0593f13..0000000 --- a/thoughts-backend/.env +++ /dev/null @@ -1,8 +0,0 @@ -HOST=0.0.0.0 -PORT=8000 -#DATABASE_URL="sqlite://dev.db" -DATABASE_URL="postgresql://postgres:postgres@localhost/thoughts" -#DATABASE_URL=postgres://thoughts_user:postgres@database:5432/thoughts_db -PREFORK=0 -AUTH_SECRET=your_secret_key_here -BASE_URL=http://0.0.0.0 \ No newline at end of file diff --git a/thoughts-backend/.env.example b/thoughts-backend/.env.example deleted file mode 100644 index 87dccd6..0000000 --- a/thoughts-backend/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -HOST=0.0.0.0 -PORT=3000 -DATABASE_URL="postgresql://postgres:postgres@localhost/clean-axum" -PREFORK=1 -AUTH_SECRET=your_secret_key_here -BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/thoughts-backend/.gitignore b/thoughts-backend/.gitignore deleted file mode 100644 index 0b745e2..0000000 --- a/thoughts-backend/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -.env \ No newline at end of file diff --git a/thoughts-backend/Cargo.lock b/thoughts-backend/Cargo.lock deleted file mode 100644 index ec4be89..0000000 --- a/thoughts-backend/Cargo.lock +++ /dev/null @@ -1,5093 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "api" -version = "0.1.0" -dependencies = [ - "anyhow", - "app", - "axum", - "bcrypt", - "dotenvy", - "jsonwebtoken", - "models", - "once_cell", - "reqwest", - "sea-orm", - "serde", - "serde_json", - "tokio", - "tower", - "tower-cookies", - "tower-http", - "tracing", - "utoipa", - "validator", -] - -[[package]] -name = "app" -version = "0.1.0" -dependencies = [ - "bcrypt", - "chrono", - "models", - "rand 0.8.5", - "sea-orm", - "validator", -] - -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", - "tokio", -] - -[[package]] -name = "async-io" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.4.0", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "axum" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" -dependencies = [ - "axum-core", - "axum-macros", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bcrypt" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" -dependencies = [ - "base64", - "blowfish", - "getrandom 0.3.3", - "subtle", - "zeroize", -] - -[[package]] -name = "bigdecimal" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" -dependencies = [ - "autocfg", - "libm", - "num-bigint", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - -[[package]] -name = "borsh" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "common" -version = "0.1.0" -dependencies = [ - "sea-orm", - "sea-query", - "serde", - "utoipa", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.104", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derive_arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "diesel" -version = "2.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a917a9209950404d5be011c81d081a2692a822f73c3d6af586f0cab5ff50f614" -dependencies = [ - "bitflags", - "byteorder", - "diesel_derives", - "itoa", -] - -[[package]] -name = "diesel-async" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a307ac00f7c23f526a04a77761a0519b9f0eb2838ebf5b905a58580095bdcb" -dependencies = [ - "async-trait", - "diesel", - "futures-util", - "scoped-futures", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "diesel_derives" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52841e97814f407b895d836fa0012091dff79c6268f39ad8155d384c21ae0d26" -dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" -dependencies = [ - "syn 2.0.104", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "doc" -version = "0.1.0" -dependencies = [ - "axum", - "models", - "tracing", - "utoipa", - "utoipa-scalar", - "utoipa-swagger-ui", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dsl_auto_type" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" -dependencies = [ - "darling", - "either", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.0", - "pin-project-lite", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "libz-rs-sys", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.4", -] - -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -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 = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.1", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" -dependencies = [ - "equivalent", - "hashbrown 0.15.4", - "serde", -] - -[[package]] -name = "inherent" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "9.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" -dependencies = [ - "base64", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-rs-sys" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" -dependencies = [ - "zlib-rs", -] - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -dependencies = [ - "value-bag", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "migration" -version = "0.1.0" -dependencies = [ - "async-std", - "models", - "sea-orm-migration", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "models" -version = "0.1.0" -dependencies = [ - "common", - "sea-orm", - "serde", - "serde_json", - "utoipa", - "uuid", - "validator", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pem" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" -dependencies = [ - "base64", - "serde", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pgvector" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" -dependencies = [ - "serde", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "polling" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" -dependencies = [ - "base64", - "byteorder", - "bytes", - "fallible-iterator", - "hmac", - "md-5", - "memchr", - "rand 0.9.1", - "sha2", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prefork" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a2d720695b23e76e222719e76afe3639187f3e222868631dca8e3dc2b92c65" -dependencies = [ - "log", - "nix", - "signal-hook", - "thiserror 2.0.12", - "tokio", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "version_check", - "yansi", -] - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "quinn" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand 0.9.1", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[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 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.1", -] - -[[package]] -name = "reqwest-middleware" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" -dependencies = [ - "anyhow", - "async-trait", - "http", - "reqwest", - "serde", - "thiserror 1.0.69", - "tower-service", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rust-embed" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.104", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" -dependencies = [ - "sha2", - "walkdir", -] - -[[package]] -name = "rust_decimal" -version = "1.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scoped-futures" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sea-bae" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" -dependencies = [ - "heck 0.4.1", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "sea-orm" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18b7272b88bd608cd846de24f41b74a0315a135fe761b0aed4ec1ce6a6327a93" -dependencies = [ - "async-stream", - "async-trait", - "bigdecimal", - "chrono", - "futures-util", - "log", - "ouroboros", - "pgvector", - "rust_decimal", - "sea-orm-macros", - "sea-query", - "sea-query-binder", - "serde", - "serde_json", - "sqlx", - "strum 0.26.3", - "thiserror 2.0.12", - "time", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "sea-orm-cli" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a4961b0d9098a9dc992d6e75fb761f9e5c442bb46746eeffa08e47b53759fce" -dependencies = [ - "chrono", - "clap", - "dotenvy", - "glob", - "regex", - "sea-schema", - "sqlx", - "tracing", - "tracing-subscriber", - "url", -] - -[[package]] -name = "sea-orm-macros" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c38255a6b2e6d1ae2d5df35696507a345f03c036ae32caeb0a3b922dbab610d" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "sea-bae", - "syn 2.0.104", - "unicode-ident", -] - -[[package]] -name = "sea-orm-migration" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f58c3b1dcf6c137f08394f0228f9baf1574a2a799e93dc5da3cd9228bef9c5" -dependencies = [ - "async-trait", - "clap", - "dotenvy", - "sea-orm", - "sea-orm-cli", - "sea-schema", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "sea-query" -version = "0.32.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c91783d1514b99754fc6a4079081dcc2c587dadbff65c48c7f62297443536a" -dependencies = [ - "bigdecimal", - "chrono", - "inherent", - "ordered-float", - "rust_decimal", - "sea-query-derive", - "serde_json", - "time", - "uuid", -] - -[[package]] -name = "sea-query-binder" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" -dependencies = [ - "bigdecimal", - "chrono", - "rust_decimal", - "sea-query", - "serde_json", - "sqlx", - "time", - "uuid", -] - -[[package]] -name = "sea-query-derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" -dependencies = [ - "darling", - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.104", - "thiserror 2.0.12", -] - -[[package]] -name = "sea-schema" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338" -dependencies = [ - "futures", - "sea-query", - "sea-query-binder", - "sea-schema-derive", - "sqlx", -] - -[[package]] -name = "sea-schema-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shuttle-api-client" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02461765f28a0c0d188d891415b9a4c7fbe6fed10462ecc152978730975cc773" -dependencies = [ - "anyhow", - "async-trait", - "headers", - "http", - "percent-encoding", - "reqwest", - "reqwest-middleware", - "serde", - "serde_json", - "shuttle-common", - "tokio", - "tokio-tungstenite", - "url", -] - -[[package]] -name = "shuttle-axum" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf1e9636f7f77bb6d1eb9e0e19352eb8cbaf11109fdbf5148cabd8d28190161" -dependencies = [ - "axum", - "shuttle-runtime", -] - -[[package]] -name = "shuttle-codegen" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140bb12bb6fb5ed68ec56d226e3990e1a91487d27b86b424b35a8f0e6ee66f63" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "shuttle-common" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33df336332f0052b047470b321709f70475a8f4d94909d23bc67d448aad15454" -dependencies = [ - "chrono", - "http", - "semver", - "serde", - "serde_json", - "strum 0.27.1", - "typeshare", - "zeroize", -] - -[[package]] -name = "shuttle-runtime" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb615de9fb1f11dd581af68ceec78425ac0bc19c7e3af9ae1a16605358a288f" -dependencies = [ - "anyhow", - "async-trait", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "serde", - "serde_json", - "shuttle-api-client", - "shuttle-codegen", - "shuttle-common", - "shuttle-service", - "strfmt", - "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "shuttle-service" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2eaff065874fa4d56c66eed11e9534331ce19e018cdb3fe3cfc5c251beff723" -dependencies = [ - "anyhow", - "async-trait", - "serde", - "shuttle-common", - "strfmt", - "thiserror 2.0.12", -] - -[[package]] -name = "shuttle-shared-db" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0bd611f4fb80c3cd9291312a2b8db23fe0868d80d85288c5ea7e374a77e3f86" -dependencies = [ - "async-trait", - "diesel-async", - "serde", - "serde_json", - "shuttle-service", - "sqlx", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simple_asn1" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror 2.0.12", - "time", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bigdecimal", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.4.0", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.4", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rust_decimal", - "rustls", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.12", - "time", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.104", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.104", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bigdecimal", - "bitflags", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "rust_decimal", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.12", - "time", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bigdecimal", - "bitflags", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "num-bigint", - "once_cell", - "rand 0.8.5", - "rust_decimal", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.12", - "time", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.12", - "time", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strfmt" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.104", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.60.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "thoughts-backend" -version = "0.1.0" -dependencies = [ - "api", - "app", - "axum", - "doc", - "http-body-util", - "models", - "prefork", - "sea-orm", - "serde_json", - "shuttle-axum", - "shuttle-runtime", - "shuttle-shared-db", - "tokio", - "tracing", - "tracing-subscriber", - "utils", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-postgres" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.9.1", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots 0.26.11", -] - -[[package]] -name = "tokio-util" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-cookies" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" -dependencies = [ - "axum-core", - "cookie", - "futures-util", - "http", - "parking_lot", - "pin-project-lite", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "http-range-header", - "httpdate", - "iri-string", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.1", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 2.0.12", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "typeshare" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19be0f411120091e76e13e5a0186d8e2bcc3e7e244afdb70152197f1a8486ceb" -dependencies = [ - "chrono", - "serde", - "serde_json", - "typeshare-annotation", -] - -[[package]] -name = "typeshare-annotation" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" -dependencies = [ - "quote", - "syn 2.0.104", -] - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "axum", - "migration", - "sea-orm", - "tokio", - "tower", - "uuid", -] - -[[package]] -name = "utoipa" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" -dependencies = [ - "indexmap", - "serde", - "serde_json", - "utoipa-gen", -] - -[[package]] -name = "utoipa-gen" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.104", - "uuid", -] - -[[package]] -name = "utoipa-scalar" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719" -dependencies = [ - "axum", - "serde", - "serde_json", - "utoipa", -] - -[[package]] -name = "utoipa-swagger-ui" -version = "9.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" -dependencies = [ - "axum", - "base64", - "mime_guess", - "regex", - "rust-embed", - "serde", - "serde_json", - "utoipa", - "utoipa-swagger-ui-vendored", - "zip", -] - -[[package]] -name = "utoipa-swagger-ui-vendored" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" - -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "validator" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" -dependencies = [ - "idna", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_derive", -] - -[[package]] -name = "validator_derive" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" -dependencies = [ - "darling", - "once_cell", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "value-bag" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.1", -] - -[[package]] -name = "webpki-roots" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" -dependencies = [ - "redox_syscall", - "wasite", - "web-sys", -] - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[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-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "zip" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap", - "memchr", - "zopfli", -] - -[[package]] -name = "zlib-rs" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" - -[[package]] -name = "zopfli" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] diff --git a/thoughts-backend/Cargo.toml b/thoughts-backend/Cargo.toml deleted file mode 100644 index 40b7fca..0000000 --- a/thoughts-backend/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "thoughts-backend" -version = "0.1.0" -edition = "2021" -publish = false - -# docs -authors = ["Gabriel Kaszewski "] -description = "Thoughts backend" -license = "MIT" -readme = "README.md" - -[workspace] -members = ["api", "app", "doc", "models", "migration", "utils"] - -[workspace.dependencies] -tower = { version = "0.5.2", default-features = false } -axum = { version = "0.8.4", default-features = false } -sea-orm = { version = "1.1.12" } -sea-query = { version = "0.32.6" } # Added sea-query dependency -serde = { version = "1.0.219", features = ["derive"] } -serde_json = { version = "1.0.140", features = ["raw_value"] } -tracing = "0.1.41" -utoipa = { version = "5.4.0", features = ["macros", "chrono", "uuid"] } -validator = { version = "0.20.0", default-features = false } -chrono = { version = "0.4.41", features = ["serde"] } -tokio = { version = "1.45.1", features = ["full"] } - -[dependencies] -api = { path = "api" } -utils = { path = "utils" } -doc = { path = "doc" } - -sea-orm = { workspace = true } - -# logging -tracing = { workspace = true } -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } - -# runtime -axum = { workspace = true, features = ["tokio", "http1", "http2"] } -prefork = { version = "0.6.0", default-features = false, optional = true } -tokio = { version = "1.45.1", features = ["full"] } - -# shuttle runtime -shuttle-axum = { version = "0.55.0", optional = true } -shuttle-runtime = { version = "0.55.0", optional = true } -shuttle-shared-db = { version = "0.55.0", features = [ - "postgres", -], optional = true } - -[dev-dependencies] -app = { path = "app" } -models = { path = "models" } - -http-body-util = "0.1.3" -serde_json = { workspace = true } - -[features] -default = ["prefork"] -prefork = ["prefork/tokio"] -shuttle = ["shuttle-axum", "shuttle-runtime", "shuttle-shared-db"] diff --git a/thoughts-backend/Dockerfile b/thoughts-backend/Dockerfile deleted file mode 100644 index 799e60d..0000000 --- a/thoughts-backend/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -FROM rust:1.89-slim AS builder - -RUN apt-get update && apt-get install -y libssl-dev pkg-config && rm -rf /var/lib/apt/lists/* - -RUN cargo install cargo-chef --locked -WORKDIR /app - -COPY Cargo.toml Cargo.lock ./ -COPY api/Cargo.toml ./api/ -COPY app/Cargo.toml ./app/ -COPY common/Cargo.toml ./common/ -COPY doc/Cargo.toml ./doc/ -COPY migration/Cargo.toml ./migration/ -COPY models/Cargo.toml ./models/ -COPY utils/Cargo.toml ./utils/ - -RUN mkdir -p src && echo "fn main() {}" > src/main.rs - -RUN cargo chef prepare --recipe-path recipe.json - -RUN cargo chef cook --release --recipe-path recipe.json - -COPY . . - -RUN cargo build --release --bin thoughts-backend - -FROM debian:13-slim AS runtime - -RUN apt-get update && apt-get install -y --no-install-recommends openssl wget && rm -rf /var/lib/apt/lists/* - -RUN groupadd --system --gid 1001 appgroup && \ - useradd --system --uid 1001 --gid appgroup appuser - -WORKDIR /app - -COPY --from=builder /app/target/release/thoughts-backend . - -RUN chown -R appuser:appgroup /app - -USER appuser - -EXPOSE 8000 - -CMD ["./thoughts-backend"] \ No newline at end of file diff --git a/thoughts-backend/LICENSE b/thoughts-backend/LICENSE deleted file mode 100644 index a486a5c..0000000 --- a/thoughts-backend/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Weiliang Li - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/thoughts-backend/README.md b/thoughts-backend/README.md deleted file mode 100644 index 2993145..0000000 --- a/thoughts-backend/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# ⚠️ DEPRECATED — thoughts-backend (v1) - -> **This directory is the original v1 implementation and is no longer maintained.** -> It will be removed in a future release. - -## Use v2 instead - -The active codebase lives at the **repository root** (`/crates/`). It is a complete rewrite with: - -- Hexagonal (Ports & Adapters) architecture -- Full ActivityPub federation -- Remote actor discovery and profile browsing -- NATS JetStream event bus -- Clean REST API with content negotiation -- Next.js frontend (`/thoughts-frontend/`) - -Do not build, run, or modify anything in this directory. diff --git a/thoughts-backend/api/Cargo.toml b/thoughts-backend/api/Cargo.toml deleted file mode 100644 index 1017f4b..0000000 --- a/thoughts-backend/api/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "api" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "api" -path = "src/lib.rs" - -[dependencies] -axum = { workspace = true, features = ["macros", "query"] } -serde = { workspace = true } -tower = { workspace = true } -tracing = { workspace = true } -validator = { workspace = true, features = ["derive"] } -bcrypt = "0.17.1" -jsonwebtoken = "9.3.1" -once_cell = "1.21.3" - - -# db -sea-orm = { workspace = true } - -# doc -utoipa = { workspace = true } - -serde_json = { workspace = true } -tokio = { workspace = true } - -# local dependencies -app = { path = "../app" } -models = { path = "../models" } -reqwest = { version = "0.12.23", features = ["json"] } - - -tower-http = { version = "0.6.6", features = ["fs", "cors"] } -tower-cookies = "0.11.0" -anyhow = "1.0.98" -dotenvy = "0.15.7" - -[dev-dependencies] diff --git a/thoughts-backend/api/src/error/adapter.rs b/thoughts-backend/api/src/error/adapter.rs deleted file mode 100644 index 2d20937..0000000 --- a/thoughts-backend/api/src/error/adapter.rs +++ /dev/null @@ -1,41 +0,0 @@ -use axum::{extract::rejection::JsonRejection, http::StatusCode}; -use sea_orm::DbErr; - -use app::error::UserError; - -use super::traits::HTTPError; - -impl HTTPError for JsonRejection { - fn to_status_code(&self) -> StatusCode { - match self { - JsonRejection::JsonSyntaxError(_) => StatusCode::BAD_REQUEST, - _ => StatusCode::BAD_REQUEST, - } - } -} - -impl HTTPError for DbErr { - fn to_status_code(&self) -> StatusCode { - match self { - DbErr::ConnectionAcquire(_) => StatusCode::INTERNAL_SERVER_ERROR, - DbErr::UnpackInsertId => StatusCode::CONFLICT, - DbErr::RecordNotFound(_) => StatusCode::NOT_FOUND, - DbErr::Custom(s) if s == "Users cannot follow themselves" => StatusCode::BAD_REQUEST, - _ => StatusCode::INTERNAL_SERVER_ERROR, // TODO:: more granularity - } - } -} - -impl HTTPError for UserError { - fn to_status_code(&self) -> StatusCode { - match self { - UserError::NotFound => StatusCode::NOT_FOUND, - UserError::NotFollowing => StatusCode::NOT_FOUND, - UserError::Forbidden => StatusCode::FORBIDDEN, - UserError::UsernameTaken => StatusCode::BAD_REQUEST, - UserError::AlreadyFollowing => StatusCode::BAD_REQUEST, - UserError::Validation(_) => StatusCode::UNPROCESSABLE_ENTITY, - UserError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} diff --git a/thoughts-backend/api/src/error/core.rs b/thoughts-backend/api/src/error/core.rs deleted file mode 100644 index 60627db..0000000 --- a/thoughts-backend/api/src/error/core.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub struct ApiError(pub(super) anyhow::Error); - -impl From for ApiError -where - E: Into, -{ - fn from(err: E) -> Self { - Self(err.into()) - } -} diff --git a/thoughts-backend/api/src/error/handler.rs b/thoughts-backend/api/src/error/handler.rs deleted file mode 100644 index 40a8522..0000000 --- a/thoughts-backend/api/src/error/handler.rs +++ /dev/null @@ -1,36 +0,0 @@ -use axum::{ - extract::rejection::JsonRejection, - http::StatusCode, - response::{IntoResponse, Response}, - Json, -}; -use sea_orm::DbErr; - -use app::error::UserError; - -use super::{ApiError, HTTPError}; -use crate::models::ApiErrorResponse; - -impl IntoResponse for ApiError { - fn into_response(self) -> Response { - let err = self.0; - - let (status, message) = if let Some(err) = err.downcast_ref::() { - tracing::error!(%err, "error from db:"); - (err.to_status_code(), "DB error".to_string()) // hide the detail - } else if let Some(err) = err.downcast_ref::() { - (err.to_status_code(), err.to_string()) - } else if let Some(err) = err.downcast_ref::() { - tracing::error!(%err, "error from extractor:"); - (err.to_status_code(), err.to_string()) - } else { - tracing::error!(%err, "error from other source:"); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Unknown error".to_string(), - ) - }; - - (status, Json(ApiErrorResponse { message })).into_response() - } -} diff --git a/thoughts-backend/api/src/error/mod.rs b/thoughts-backend/api/src/error/mod.rs deleted file mode 100644 index e20d4f9..0000000 --- a/thoughts-backend/api/src/error/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod adapter; -mod core; -mod handler; -mod traits; - -pub use core::ApiError; -pub use traits::HTTPError; diff --git a/thoughts-backend/api/src/error/traits.rs b/thoughts-backend/api/src/error/traits.rs deleted file mode 100644 index e3d59c0..0000000 --- a/thoughts-backend/api/src/error/traits.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::http::StatusCode; - -pub trait HTTPError { - fn to_status_code(&self) -> StatusCode; -} diff --git a/thoughts-backend/api/src/extractor/auth.rs b/thoughts-backend/api/src/extractor/auth.rs deleted file mode 100644 index d324146..0000000 --- a/thoughts-backend/api/src/extractor/auth.rs +++ /dev/null @@ -1,76 +0,0 @@ -use axum::{ - extract::FromRequestParts, - http::{request::Parts, HeaderMap, StatusCode}, -}; - -use jsonwebtoken::{decode, DecodingKey, Validation}; -use once_cell::sync::Lazy; -use sea_orm::prelude::Uuid; -use serde::{Deserialize, Serialize}; - -use app::{persistence::api_key, state::AppState}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Claims { - pub sub: Uuid, - pub exp: usize, -} - -static JWT_SECRET: Lazy = - Lazy::new(|| std::env::var("AUTH_SECRET").expect("AUTH_SECRET must be set")); - -pub struct AuthUser { - pub id: Uuid, -} - -impl FromRequestParts for AuthUser { - type Rejection = (StatusCode, &'static str); - - async fn from_request_parts( - parts: &mut Parts, - state: &AppState, - ) -> Result { - // --- Test User ID (Keep for testing) --- - if let Some(user_id_header) = parts.headers.get("x-test-user-id") { - let user_id_str = user_id_header.to_str().unwrap_or("0"); - let user_id = user_id_str.parse::().unwrap_or(Uuid::nil()); - return Ok(AuthUser { id: user_id }); - } - - // --- API Key Authentication --- - if let Some(api_key) = get_api_key_from_header(&parts.headers) { - return match api_key::validate_api_key(&state.conn, &api_key).await { - Ok(user) => Ok(AuthUser { id: user.id }), - Err(_) => Err((StatusCode::UNAUTHORIZED, "Invalid API Key")), - }; - } - - // --- JWT Authentication (Fallback) --- - let token = get_token_from_header(&parts.headers) - .ok_or((StatusCode::UNAUTHORIZED, "Missing or invalid token"))?; - - let decoding_key = DecodingKey::from_secret(JWT_SECRET.as_ref()); - - let claims = decode::(&token, &decoding_key, &Validation::default()) - .map(|data| data.claims) - .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token"))?; - - Ok(AuthUser { id: claims.sub }) - } -} - -fn get_token_from_header(headers: &HeaderMap) -> Option { - headers - .get("Authorization") - .and_then(|header| header.to_str().ok()) - .and_then(|header| header.strip_prefix("Bearer ")) - .map(|token| token.to_owned()) -} - -fn get_api_key_from_header(headers: &HeaderMap) -> Option { - headers - .get("Authorization") - .and_then(|header| header.to_str().ok()) - .and_then(|header| header.strip_prefix("ApiKey ")) - .map(|key| key.to_owned()) -} diff --git a/thoughts-backend/api/src/extractor/json.rs b/thoughts-backend/api/src/extractor/json.rs deleted file mode 100644 index face9de..0000000 --- a/thoughts-backend/api/src/extractor/json.rs +++ /dev/null @@ -1,26 +0,0 @@ -use axum::{ - extract::FromRequest, - response::{IntoResponse, Response}, -}; -use validator::Validate; - -use crate::error::ApiError; - -#[derive(FromRequest)] -#[from_request(via(axum::Json), rejection(ApiError))] -pub struct Json(pub T); - -impl IntoResponse for Json -where - axum::Json: IntoResponse, -{ - fn into_response(self) -> Response { - axum::Json(self.0).into_response() - } -} - -impl Validate for Json { - fn validate(&self) -> Result<(), validator::ValidationErrors> { - self.0.validate() - } -} diff --git a/thoughts-backend/api/src/extractor/mod.rs b/thoughts-backend/api/src/extractor/mod.rs deleted file mode 100644 index 05b32f7..0000000 --- a/thoughts-backend/api/src/extractor/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod auth; -mod json; -mod optional_auth; -mod valid; - -pub use auth::AuthUser; -pub use auth::Claims; -pub use json::Json; -pub use optional_auth::OptionalAuthUser; -pub use valid::Valid; diff --git a/thoughts-backend/api/src/extractor/optional_auth.rs b/thoughts-backend/api/src/extractor/optional_auth.rs deleted file mode 100644 index faa4989..0000000 --- a/thoughts-backend/api/src/extractor/optional_auth.rs +++ /dev/null @@ -1,21 +0,0 @@ -use super::AuthUser; -use crate::error::ApiError; -use app::state::AppState; -use axum::{extract::FromRequestParts, http::request::Parts}; - -pub struct OptionalAuthUser(pub Option); - -impl FromRequestParts for OptionalAuthUser { - type Rejection = ApiError; - - async fn from_request_parts( - parts: &mut Parts, - state: &AppState, - ) -> Result { - match AuthUser::from_request_parts(parts, state).await { - Ok(user) => Ok(OptionalAuthUser(Some(user))), - // If the user is not authenticated for any reason, we just treat them as a guest. - Err(_) => Ok(OptionalAuthUser(None)), - } - } -} diff --git a/thoughts-backend/api/src/extractor/valid.rs b/thoughts-backend/api/src/extractor/valid.rs deleted file mode 100644 index 89b808e..0000000 --- a/thoughts-backend/api/src/extractor/valid.rs +++ /dev/null @@ -1,23 +0,0 @@ -use axum::extract::{FromRequest, Request}; -use validator::Validate; - -use crate::validation::ValidRejection; - -#[derive(Debug, Clone, Copy, Default)] -pub struct Valid(pub T); - -impl FromRequest for Valid -where - State: Send + Sync, - Extractor: Validate + FromRequest, -{ - type Rejection = ValidRejection<>::Rejection>; - - async fn from_request(req: Request, state: &State) -> Result { - let inner = Extractor::from_request(req, state) - .await - .map_err(ValidRejection::Extractor)?; - inner.validate()?; - Ok(Valid(inner)) - } -} diff --git a/thoughts-backend/api/src/init.rs b/thoughts-backend/api/src/init.rs deleted file mode 100644 index a74c69a..0000000 --- a/thoughts-backend/api/src/init.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::time::Duration; - -use axum::Router; -use sea_orm::{ConnectOptions, Database, DatabaseConnection}; - -use app::config::Config; -use app::state::AppState; - -use crate::routers::create_router; - -pub fn setup_router(conn: DatabaseConnection, config: &Config) -> Router { - create_router(AppState { - conn, - base_url: config.base_url.clone(), - }) -} - -pub fn setup_config() -> Config { - dotenvy::dotenv().ok(); - Config::from_env() -} - -pub async fn setup_db(db_url: &str, prefork: bool) -> DatabaseConnection { - let mut opt = ConnectOptions::new(db_url); - opt.max_lifetime(Duration::from_secs(60)); - - if !prefork { - opt.min_connections(10).max_connections(100); - } - - Database::connect(opt) - .await - .expect("Database connection failed") -} diff --git a/thoughts-backend/api/src/lib.rs b/thoughts-backend/api/src/lib.rs deleted file mode 100644 index ceba9b9..0000000 --- a/thoughts-backend/api/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod error; -mod extractor; -mod init; -mod validation; - -pub mod models; -pub mod routers; - -pub use init::{setup_config, setup_db, setup_router}; diff --git a/thoughts-backend/api/src/models/mod.rs b/thoughts-backend/api/src/models/mod.rs deleted file mode 100644 index 0ec3a2f..0000000 --- a/thoughts-backend/api/src/models/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod response; - -pub use response::{ApiErrorResponse, ParamsErrorResponse, ValidationErrorResponse}; diff --git a/thoughts-backend/api/src/models/response.rs b/thoughts-backend/api/src/models/response.rs deleted file mode 100644 index 7d289ad..0000000 --- a/thoughts-backend/api/src/models/response.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::HashMap; - -use serde::Serialize; -use utoipa::ToSchema; - -#[derive(Serialize, ToSchema)] -pub struct ApiErrorResponse { - pub message: String, -} - -#[derive(Serialize, ToSchema)] -pub struct ValidationErrorResponse { - pub message: String, - pub details: T, -} - -pub type ParamsErrorResponse = - ValidationErrorResponse>>>; - -impl From for ValidationErrorResponse { - fn from(t: T) -> Self { - Self { - message: "Validation error".to_string(), - details: t, - } - } -} diff --git a/thoughts-backend/api/src/routers/api_key.rs b/thoughts-backend/api/src/routers/api_key.rs deleted file mode 100644 index c3d135f..0000000 --- a/thoughts-backend/api/src/routers/api_key.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{ - error::ApiError, - extractor::{AuthUser, Json}, - models::ApiErrorResponse, -}; -use app::{persistence::api_key, state::AppState}; -use axum::{ - extract::{Path, State}, - http::StatusCode, - response::IntoResponse, - routing::{delete, get}, - Router, -}; -use models::schemas::api_key::{ApiKeyListSchema, ApiKeyRequest, ApiKeyResponse}; -use sea_orm::prelude::Uuid; - -#[utoipa::path( - get, - path = "", - responses( - (status = 200, description = "List of API keys", body = ApiKeyListSchema), - (status = 401, description = "Unauthorized", body = ApiErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ), - security( - ("bearerAuth" = []) - ) -)] -async fn get_keys( - State(state): State, - auth_user: AuthUser, -) -> Result { - let keys = api_key::get_api_keys_for_user(&state.conn, auth_user.id).await?; - Ok(Json(ApiKeyListSchema::from(keys))) -} - -#[utoipa::path( - post, - path = "", - request_body = ApiKeyRequest, - responses( - (status = 201, description = "API key created", body = ApiKeyResponse), - (status = 400, description = "Bad request", body = ApiErrorResponse), - (status = 401, description = "Unauthorized", body = ApiErrorResponse), - (status = 422, description = "Validation error", body = ApiErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ), - security( - ("bearerAuth" = []) - ) -)] -async fn create_key( - State(state): State, - auth_user: AuthUser, - Json(params): Json, -) -> Result { - let (key_model, plaintext_key) = - api_key::create_api_key(&state.conn, auth_user.id, params.name).await?; - - let response = ApiKeyResponse::from_parts(key_model, Some(plaintext_key)); - Ok((StatusCode::CREATED, Json(response))) -} - -#[utoipa::path( - delete, - path = "/{key_id}", - responses( - (status = 204, description = "API key deleted"), - (status = 401, description = "Unauthorized", body = ApiErrorResponse), - (status = 404, description = "API key not found", body = ApiErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ), - params( - ("key_id" = Uuid, Path, description = "The ID of the API key to delete") - ), - security( - ("bearerAuth" = []) - ) -)] -async fn delete_key( - State(state): State, - auth_user: AuthUser, - Path(key_id): Path, -) -> Result { - api_key::delete_api_key(&state.conn, key_id, auth_user.id).await?; - Ok(StatusCode::NO_CONTENT) -} - -pub fn create_api_key_router() -> Router { - Router::new() - .route("/", get(get_keys).post(create_key)) - .route("/{key_id}", delete(delete_key)) -} diff --git a/thoughts-backend/api/src/routers/auth.rs b/thoughts-backend/api/src/routers/auth.rs deleted file mode 100644 index bf3ab89..0000000 --- a/thoughts-backend/api/src/routers/auth.rs +++ /dev/null @@ -1,93 +0,0 @@ -use axum::{ - debug_handler, extract::State, http::StatusCode, response::IntoResponse, routing::post, Router, -}; -use jsonwebtoken::{encode, EncodingKey, Header}; -use once_cell::sync::Lazy; -use serde::Serialize; -use std::time::{SystemTime, UNIX_EPOCH}; -use utoipa::ToSchema; - -use crate::{ - error::ApiError, - extractor::{Claims, Json, Valid}, - models::{ApiErrorResponse, ParamsErrorResponse}, -}; -use app::{persistence::auth, state::AppState}; -use models::{ - params::auth::{LoginParams, RegisterParams}, - schemas::user::UserSchema, -}; - -static JWT_SECRET: Lazy = - Lazy::new(|| std::env::var("AUTH_SECRET").expect("AUTH_SECRET must be set")); - -#[derive(Serialize, ToSchema)] -pub struct TokenResponse { - token: String, -} - -#[utoipa::path( - post, - path = "/register", - request_body = RegisterParams, - responses( - (status = 201, description = "User registered", body = UserSchema), - (status = 400, description = "Bad request", body = ApiErrorResponse), - (status = 409, description = "Username already exists", body = ApiErrorResponse), - (status = 422, description = "Validation error", body = ParamsErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ) -)] -#[axum::debug_handler] -async fn register( - State(state): State, - Valid(Json(params)): Valid>, -) -> Result { - let user = auth::register_user(&state.conn, params).await?; - Ok((StatusCode::CREATED, Json(UserSchema::from(user)))) -} - -#[utoipa::path( - post, - path = "/login", - request_body = LoginParams, - responses( - (status = 200, description = "User logged in", body = TokenResponse), - (status = 400, description = "Bad request", body = ApiErrorResponse), - (status = 401, description = "Invalid credentials", body = ApiErrorResponse), - (status = 422, description = "Validation error", body = ParamsErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ) -)] -#[debug_handler] -async fn login( - state: State, - Valid(Json(params)): Valid>, -) -> Result { - let user = auth::authenticate_user(&state.conn, params).await?; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - let claims = Claims { - sub: user.id, - exp: (now + 3600 * 24) as usize, - }; - - let token = encode( - &Header::default(), - &claims, - &EncodingKey::from_secret(JWT_SECRET.as_ref()), - ) - .map_err(|e| ApiError::from(app::error::UserError::Internal(e.to_string())))?; - - Ok((StatusCode::OK, Json(TokenResponse { token }))) -} - -pub fn create_auth_router() -> Router { - Router::new() - .route("/register", post(register)) - .route("/login", post(login)) -} diff --git a/thoughts-backend/api/src/routers/feed.rs b/thoughts-backend/api/src/routers/feed.rs deleted file mode 100644 index 39cfb03..0000000 --- a/thoughts-backend/api/src/routers/feed.rs +++ /dev/null @@ -1,67 +0,0 @@ -use axum::{ - extract::{Query, State}, - response::IntoResponse, - routing::get, - Json, Router, -}; - -use app::{ - persistence::{follow::get_following_ids, thought::get_feed_for_users_and_self_paginated}, - state::AppState, -}; -use models::{ - queries::pagination::PaginationQuery, - schemas::{pagination::PaginatedResponse, thought::ThoughtSchema}, -}; - -use crate::{error::ApiError, extractor::AuthUser}; - -#[utoipa::path( - get, - path = "", - params(PaginationQuery), - responses( - (status = 200, description = "Authenticated user's feed", body = PaginatedResponse) - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn feed_get( - State(state): State, - auth_user: AuthUser, - Query(pagination): Query, -) -> Result { - let following_ids = get_following_ids(&state.conn, auth_user.id).await?; - let (thoughts_with_authors, total_items) = get_feed_for_users_and_self_paginated( - &state.conn, - auth_user.id, - following_ids, - &pagination, - ) - .await?; - - let thoughts_schema: Vec = thoughts_with_authors - .into_iter() - .map(ThoughtSchema::from) - .collect(); - - let page = pagination.page(); - let page_size = pagination.page_size(); - let total_pages = (total_items as f64 / page_size as f64).ceil() as u64; - - let response = PaginatedResponse { - items: thoughts_schema, - total_items, - total_pages, - page, - page_size, - }; - - Ok(Json(response)) -} - -pub fn create_feed_router() -> Router { - Router::new().route("/", get(feed_get)) -} diff --git a/thoughts-backend/api/src/routers/friends.rs b/thoughts-backend/api/src/routers/friends.rs deleted file mode 100644 index b6e45a5..0000000 --- a/thoughts-backend/api/src/routers/friends.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{error::ApiError, extractor::AuthUser}; -use app::{persistence::user, state::AppState}; -use axum::{extract::State, response::IntoResponse, routing::get, Json, Router}; -use models::schemas::user::UserListSchema; - -#[utoipa::path( - get, - path = "", - responses( - (status = 200, description = "List of authenticated user's friends", body = UserListSchema) - ), - security(("bearer_auth" = [])) -)] -async fn get_friends_list( - State(state): State, - auth_user: AuthUser, -) -> Result { - let friends = user::get_friends(&state.conn, auth_user.id).await?; - Ok(Json(UserListSchema::from(friends))) -} - -pub fn create_friends_router() -> Router { - Router::new().route("/", get(get_friends_list)) -} diff --git a/thoughts-backend/api/src/routers/mod.rs b/thoughts-backend/api/src/routers/mod.rs deleted file mode 100644 index 9c2895e..0000000 --- a/thoughts-backend/api/src/routers/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -use axum::Router; - -pub mod api_key; -pub mod auth; -pub mod feed; -pub mod friends; -pub mod root; -pub mod search; -pub mod tag; -pub mod thought; -pub mod user; - -use crate::routers::auth::create_auth_router; -use app::state::AppState; -use root::create_root_router; -use tower_http::cors::CorsLayer; -use user::create_user_router; - -use crate::routers::{feed::create_feed_router, thought::create_thought_router}; - -pub fn create_router(state: AppState) -> Router { - let cors = CorsLayer::permissive(); - - Router::new() - .merge(create_root_router()) - .nest("/auth", create_auth_router()) - .nest("/users", create_user_router()) - .nest("/thoughts", create_thought_router()) - .nest("/feed", create_feed_router()) - .nest("/tags", tag::create_tag_router()) - .nest("/friends", friends::create_friends_router()) - .nest("/search", search::create_search_router()) - .with_state(state) - .layer(cors) -} diff --git a/thoughts-backend/api/src/routers/root.rs b/thoughts-backend/api/src/routers/root.rs deleted file mode 100644 index bd91d07..0000000 --- a/thoughts-backend/api/src/routers/root.rs +++ /dev/null @@ -1,36 +0,0 @@ -use axum::{extract::State, http::StatusCode, routing::get, Router}; -use sea_orm::{ConnectionTrait, Statement}; - -use app::state::AppState; - -use crate::error::ApiError; - -#[utoipa::path( - get, - path = "", - responses( - (status = 200, description = "Hello world", body = String) - ) -)] -async fn root_get(state: State) -> Result { - let result = state - .conn - .query_one(Statement::from_string( - state.conn.get_database_backend(), - "SELECT 'Hello, World from DB!'", - )) - .await - .map_err(ApiError::from)?; - - result.unwrap().try_get_by(0).map_err(|e| e.into()) -} - -async fn health_check() -> StatusCode { - StatusCode::OK -} - -pub fn create_root_router() -> Router { - Router::new() - .route("/", get(root_get)) - .route("/health", get(health_check)) -} diff --git a/thoughts-backend/api/src/routers/search.rs b/thoughts-backend/api/src/routers/search.rs deleted file mode 100644 index 46fcc39..0000000 --- a/thoughts-backend/api/src/routers/search.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{error::ApiError, extractor::OptionalAuthUser}; -use app::{persistence::search, state::AppState}; -use axum::{ - extract::{Query, State}, - response::IntoResponse, - routing::get, - Json, Router, -}; -use models::schemas::{ - search::SearchResultsSchema, - thought::{ThoughtListSchema, ThoughtSchema}, - user::UserListSchema, -}; -use serde::Deserialize; -use utoipa::IntoParams; - -#[derive(Deserialize, IntoParams)] -pub struct SearchQuery { - q: String, -} - -#[utoipa::path( - get, - path = "", - params(SearchQuery), - responses((status = 200, body = SearchResultsSchema)) -)] -async fn search_all( - State(state): State, - viewer: OptionalAuthUser, - Query(query): Query, -) -> Result { - let viewer_id = viewer.0.map(|u| u.id); - - let (users, thoughts) = tokio::try_join!( - search::search_users(&state.conn, &query.q), - search::search_thoughts(&state.conn, &query.q, viewer_id) - )?; - - let thought_schemas: Vec = - thoughts.into_iter().map(ThoughtSchema::from).collect(); - - let response = SearchResultsSchema { - users: UserListSchema::from(users), - thoughts: ThoughtListSchema::from(thought_schemas), - }; - - Ok(Json(response)) -} - -pub fn create_search_router() -> Router { - Router::new().route("/", get(search_all)) -} diff --git a/thoughts-backend/api/src/routers/tag.rs b/thoughts-backend/api/src/routers/tag.rs deleted file mode 100644 index 65ae3de..0000000 --- a/thoughts-backend/api/src/routers/tag.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::{error::ApiError, extractor::OptionalAuthUser}; -use app::{ - persistence::{tag, thought::get_thoughts_by_tag_name}, - state::AppState, -}; -use axum::{ - extract::{Path, State}, - response::IntoResponse, - routing::get, - Json, Router, -}; -use models::schemas::thought::{ThoughtListSchema, ThoughtSchema}; - -#[utoipa::path( - get, - path = "{tagName}", - params(("tagName" = String, Path, description = "Tag name")), - responses((status = 200, description = "List of thoughts with a specific tag", body = ThoughtListSchema)) -)] -async fn get_thoughts_by_tag( - State(state): State, - Path(tag_name): Path, - viewer: OptionalAuthUser, -) -> Result { - let thoughts_with_authors = - get_thoughts_by_tag_name(&state.conn, &tag_name, viewer.0.map(|u| u.id)).await; - let thoughts_with_authors = thoughts_with_authors?; - let thoughts_schema: Vec = thoughts_with_authors - .into_iter() - .map(ThoughtSchema::from) - .collect(); - Ok(Json(ThoughtListSchema::from(thoughts_schema))) -} - -#[utoipa::path( - get, - path = "/popular", - responses((status = 200, description = "List of popular tags", body = Vec)) -)] -async fn get_popular_tags(State(state): State) -> Result { - let tags = tag::get_popular_tags(&state.conn).await; - println!("Fetched popular tags: {:?}", tags); - let tags = tags?; - Ok(Json(tags)) -} - -pub fn create_tag_router() -> Router { - Router::new() - .route("/{tag_name}", get(get_thoughts_by_tag)) - .route("/popular", get(get_popular_tags)) -} diff --git a/thoughts-backend/api/src/routers/thought.rs b/thoughts-backend/api/src/routers/thought.rs deleted file mode 100644 index 2fc4082..0000000 --- a/thoughts-backend/api/src/routers/thought.rs +++ /dev/null @@ -1,145 +0,0 @@ -use axum::{ - extract::{Path, State}, - http::StatusCode, - response::IntoResponse, - routing::{get, post}, - Router, -}; - -use app::{ - error::UserError, - persistence::thought::{create_thought, delete_thought, get_thought}, - state::AppState, -}; -use models::{ - params::thought::CreateThoughtParams, - schemas::thought::{ThoughtSchema, ThoughtThreadSchema}, -}; -use sea_orm::prelude::Uuid; - -use crate::{ - error::ApiError, - extractor::{AuthUser, Json, OptionalAuthUser, Valid}, - models::{ApiErrorResponse, ParamsErrorResponse}, -}; - -#[utoipa::path( - get, - path = "/{id}", - params( - ("id" = Uuid, Path, description = "Thought ID") - ), - responses( - (status = 200, description = "Thought found", body = ThoughtSchema), - (status = 404, description = "Not Found", body = ApiErrorResponse) - ) -)] -async fn get_thought_by_id( - State(state): State, - Path(id): Path, - viewer: OptionalAuthUser, -) -> Result { - let viewer_id = viewer.0.map(|u| u.id); - let thought = get_thought(&state.conn, id, viewer_id) - .await? - .ok_or(UserError::NotFound)?; - - let author = app::persistence::user::get_user(&state.conn, thought.author_id) - .await? - .ok_or(UserError::NotFound)?; - - let schema = ThoughtSchema::from_models(&thought, &author); - Ok(Json(schema)) -} - -#[utoipa::path( - post, - path = "", - request_body = CreateThoughtParams, - responses( - (status = 201, description = "Thought created", body = ThoughtSchema), - (status = 400, description = "Bad request", body = ApiErrorResponse), - (status = 422, description = "Validation error", body = ParamsErrorResponse) - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn thoughts_post( - State(state): State, - auth_user: AuthUser, - Valid(Json(params)): Valid>, -) -> Result { - let thought = create_thought(&state.conn, auth_user.id, params).await?; - let author = app::persistence::user::get_user(&state.conn, auth_user.id) - .await? - .ok_or(UserError::NotFound)?; // Should not happen if auth is valid - - let schema = ThoughtSchema::from_models(&thought, &author); - Ok((StatusCode::CREATED, Json(schema))) -} - -#[utoipa::path( - delete, - path = "/{id}", - params( - ("id" = i32, Path, description = "Thought ID") - ), - responses( - (status = 204, description = "Thought deleted"), - (status = 403, description = "Forbidden", body = ApiErrorResponse), - (status = 404, description = "Not Found", body = ApiErrorResponse) - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn thoughts_delete( - State(state): State, - auth_user: AuthUser, - Path(id): Path, -) -> Result { - let thought = get_thought(&state.conn, id, Some(auth_user.id)) - .await? - .ok_or(UserError::NotFound)?; - - if thought.author_id != auth_user.id { - return Err(UserError::Forbidden.into()); - } - - delete_thought(&state.conn, id).await?; - Ok(StatusCode::NO_CONTENT) -} - -#[utoipa::path( - get, - path = "/{id}/thread", - params( - ("id" = Uuid, Path, description = "Thought ID") - ), - responses( - (status = 200, description = "Thought thread found", body = ThoughtThreadSchema), - (status = 404, description = "Not Found", body = ApiErrorResponse) - ) -)] -async fn get_thought_thread( - State(state): State, - Path(id): Path, - viewer: OptionalAuthUser, -) -> Result { - let viewer_id = viewer.0.map(|u| u.id); - let thread = app::persistence::thought::get_thought_with_replies(&state.conn, id, viewer_id) - .await? - .ok_or(UserError::NotFound)?; - - Ok(Json(thread)) -} - -pub fn create_thought_router() -> Router { - Router::new() - .route("/", post(thoughts_post)) - .route("/{id}/thread", get(get_thought_thread)) - .route("/{id}", get(get_thought_by_id).delete(thoughts_delete)) -} diff --git a/thoughts-backend/api/src/routers/user.rs b/thoughts-backend/api/src/routers/user.rs deleted file mode 100644 index c7a45f2..0000000 --- a/thoughts-backend/api/src/routers/user.rs +++ /dev/null @@ -1,476 +0,0 @@ -use axum::{ - extract::{Path, Query, State}, - http::StatusCode, - response::{IntoResponse, Response}, - routing::{get, post}, - Router, -}; -use sea_orm::prelude::Uuid; -use serde_json::{json, Value}; - -use app::persistence::{ - follow, - thought::get_thoughts_by_user, - user::{ - get_all_users, get_followers, get_following, get_user, search_users, update_user_profile, - }, -}; -use app::state::AppState; -use app::{error::UserError, persistence::user::get_user_by_username}; -use models::{ - params::user::UpdateUserParams, - schemas::{pagination::PaginatedResponse, thought::ThoughtListSchema}, -}; -use models::{ - queries::pagination::PaginationQuery, - schemas::user::{MeSchema, UserListSchema, UserSchema}, -}; -use models::{queries::user::UserQuery, schemas::thought::ThoughtSchema}; - -use crate::{error::ApiError, extractor::AuthUser}; -use crate::{extractor::OptionalAuthUser, models::ApiErrorResponse}; -use crate::{ - extractor::{Json, Valid}, - routers::api_key::create_api_key_router, -}; - -#[utoipa::path( - get, - path = "", - params( - UserQuery - ), - responses( - (status = 200, description = "List users", body = UserListSchema), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ) -)] -async fn users_get( - state: State, - query: Query, -) -> Result { - let Query(query) = query; - - let users = search_users(&state.conn, query) - .await - .map_err(ApiError::from)?; - Ok(Json(UserListSchema::from(users))) -} - -#[utoipa::path( - get, - path = "/{username}/thoughts", - params( - ("username" = String, Path, description = "Username") - ), - responses( - (status = 200, description = "List of user's thoughts", body = ThoughtListSchema), - (status = 404, description = "User not found", body = ApiErrorResponse) - ) -)] -async fn user_thoughts_get( - State(state): State, - Path(username): Path, - viewer: OptionalAuthUser, -) -> Result { - let user = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - - let thoughts_with_authors = - get_thoughts_by_user(&state.conn, user.id, viewer.0.map(|u| u.id)).await?; - - let thoughts_schema: Vec = thoughts_with_authors - .into_iter() - .map(ThoughtSchema::from) - .collect(); - - Ok(Json(ThoughtListSchema::from(thoughts_schema))) -} - -#[utoipa::path( - post, - path = "/{username}/follow", - params( - ("username" = String, Path, description = "Username to follow") - ), - responses( - (status = 204, description = "User followed successfully"), - (status = 404, description = "User not found", body = ApiErrorResponse), - (status = 409, description = "Already following", body = ApiErrorResponse) - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn user_follow_post( - State(state): State, - auth_user: AuthUser, - Path(username): Path, -) -> Result { - let user_to_follow = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - - let result = follow::follow_user(&state.conn, auth_user.id, user_to_follow.id).await; - - match result { - Ok(_) => Ok(StatusCode::NO_CONTENT), - Err(e) - if matches!( - e.sql_err(), - Some(sea_orm::SqlErr::UniqueConstraintViolation { .. }) - ) => - { - Err(UserError::AlreadyFollowing.into()) - } - Err(e) => Err(e.into()), - } -} - -#[utoipa::path( - delete, - path = "/{username}/follow", - params( - ("username" = String, Path, description = "Username to unfollow") - ), - responses( - (status = 204, description = "User unfollowed successfully"), - (status = 404, description = "User not found or not being followed", body = ApiErrorResponse) - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn user_follow_delete( - State(state): State, - auth_user: AuthUser, - Path(username): Path, -) -> Result { - let user_to_unfollow = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - - follow::unfollow_user(&state.conn, auth_user.id, user_to_unfollow.id).await?; - - Ok(StatusCode::NO_CONTENT) -} - -#[utoipa::path( - post, - path = "/{username}/inbox", - request_body = Object, - description = "The ActivityPub inbox for receiving activities.", - responses( - (status = 202, description = "Activity accepted"), - (status = 400, description = "Bad Request"), - (status = 404, description = "User not found") - ) -)] -async fn user_inbox_post( - State(state): State, - Path(username): Path, - Json(activity): Json, -) -> Result { - let user = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - - let activity_type = activity["type"].as_str().unwrap_or_default(); - let actor_id = activity["actor"].as_str().unwrap_or_default(); - - tracing::debug!(target: "activitypub", "Received activity '{}' from actor '{}' in {}'s inbox", activity_type, actor_id, username); - - // For now, we only handle the "Follow" activity - if activity_type == "Follow" { - follow::add_follower(&state.conn, user.id, actor_id).await?; - } - - // Per the ActivityPub spec, we should return a 202 Accepted status - Ok(StatusCode::ACCEPTED) -} - -#[utoipa::path( - get, - path = "/{param}", - params( - ("param" = String, Path, description = "User ID or username") - ), - responses( - (status = 200, description = "User profile or ActivityPub actor", body = UserSchema, content_type = "application/json"), - (status = 200, description = "ActivityPub actor", body = Object, content_type = "application/activity+json"), - (status = 404, description = "User not found", body = ApiErrorResponse), - (status = 500, description = "Internal server error", body = ApiErrorResponse), - ), - security( - ("api_key" = []), - ("bearer_auth" = []) - ) -)] -async fn get_user_by_param( - State(state): State, - headers: axum::http::HeaderMap, - Path(param): Path, -) -> Response { - // First, try to handle it as a numeric ID. - if let Ok(id) = param.parse::() { - return match get_user(&state.conn, id).await { - Ok(Some(user)) => Json(UserSchema::from(user)).into_response(), - Ok(None) => ApiError::from(UserError::NotFound).into_response(), - Err(db_err) => ApiError::from(db_err).into_response(), - }; - } - - // If it's not a number, treat it as a username and perform content negotiation. - let username = param; - let is_activitypub_request = headers - .get(axum::http::header::ACCEPT) - .and_then(|v| v.to_str().ok()) - .map_or(false, |s| s.contains("application/activity+json")); - - if is_activitypub_request { - // This is the logic from `user_actor_get`. - match get_user_by_username(&state.conn, &username).await { - Ok(Some(user)) => { - let user_url = format!("{}/users/{}", &state.base_url, user.username); - let actor = json!({ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], - "id": user_url, - "type": "Person", - "preferredUsername": user.username, - "inbox": format!("{}/inbox", user_url), - "outbox": format!("{}/outbox", user_url), - }); - let mut headers = axum::http::HeaderMap::new(); - headers.insert( - axum::http::header::CONTENT_TYPE, - "application/activity+json".parse().unwrap(), - ); - (headers, Json(actor)).into_response() - } - Ok(None) => ApiError::from(UserError::NotFound).into_response(), - Err(e) => ApiError::from(e).into_response(), - } - } else { - match get_user_by_username(&state.conn, &username).await { - Ok(Some(user)) => { - let top_friends = app::persistence::user::get_top_friends(&state.conn, user.id) - .await - .unwrap_or_default(); - Json(UserSchema::from((user, top_friends))).into_response() - } - Ok(None) => ApiError::from(UserError::NotFound).into_response(), - Err(e) => ApiError::from(e).into_response(), - } - } -} - -#[utoipa::path( - get, - path = "/{username}/outbox", - description = "The ActivityPub outbox for sending activities.", - responses( - (status = 200, description = "Activity collection", body = Object), - (status = 404, description = "User not found") - ) -)] -async fn user_outbox_get( - State(state): State, - Path(username): Path, - viewer: OptionalAuthUser, -) -> Result { - let user = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - - let thoughts = get_thoughts_by_user(&state.conn, user.id, viewer.0.map(|u| u.id)).await?; - - // Format the outbox as an ActivityPub OrderedCollection - let outbox_url = format!("{}/users/{}/outbox", &state.base_url, username); - let items: Vec = thoughts - .into_iter() - .map(|thought| { - let thought_url = format!("{}/thoughts/{}", &state.base_url, thought.id); - let author_url = format!("{}/users/{}", &state.base_url, thought.author_username); - json!({ - "id": format!("{}/activity", thought_url), - "type": "Create", - "actor": author_url, - "published": thought.created_at, - "to": ["https://www.w3.org/ns/activitystreams#Public"], - "object": { - "id": thought_url, - "type": "Note", - "attributedTo": author_url, - "content": thought.content, - "published": thought.created_at, - } - }) - }) - .collect(); - - let outbox = json!({ - "@context": "https://www.w3.org/ns/activitystreams", - "id": outbox_url, - "type": "OrderedCollection", - "totalItems": items.len(), - "orderedItems": items, - }); - - let mut headers = axum::http::HeaderMap::new(); - headers.insert( - axum::http::header::CONTENT_TYPE, - "application/activity+json".parse().unwrap(), - ); - - Ok((headers, Json(outbox))) -} - -#[utoipa::path( - get, - path = "/me", - responses( - (status = 200, description = "Authenticated user's full profile", body = MeSchema) - ), - security( - ("bearer_auth" = []) - ) -)] -async fn get_me( - State(state): State, - auth_user: AuthUser, -) -> Result { - let user = get_user(&state.conn, auth_user.id) - .await? - .ok_or(UserError::NotFound)?; - let top_friends = app::persistence::user::get_top_friends(&state.conn, auth_user.id).await?; - - let following = get_following(&state.conn, auth_user.id).await?; - - let response = MeSchema { - id: user.id, - username: user.username, - display_name: user.display_name, - bio: user.bio, - avatar_url: user.avatar_url, - header_url: user.header_url, - custom_css: user.custom_css, - top_friends: top_friends.into_iter().map(|u| u.username).collect(), - joined_at: user.created_at.into(), - following: following.into_iter().map(UserSchema::from).collect(), - }; - Ok(axum::Json(response)) -} - -#[utoipa::path( - put, - path = "/me", - request_body = UpdateUserParams, - responses( - (status = 200, description = "Profile updated", body = UserSchema), - (status = 400, description = "Bad request", body = ApiErrorResponse), - (status = 422, description = "Validation error", body = ApiErrorResponse) - ), - security( - ("bearer_auth" = []) - ) -)] -async fn update_me( - State(state): State, - auth_user: AuthUser, - Valid(Json(params)): Valid>, -) -> Result { - let updated_user = update_user_profile(&state.conn, auth_user.id, params).await?; - Ok(axum::Json(UserSchema::from(updated_user))) -} - -#[utoipa::path( - get, - path = "/{username}/following", - responses((status = 200, body = UserListSchema)) -)] -async fn get_user_following( - State(state): State, - Path(username): Path, -) -> Result { - let user = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - let following_list = get_following(&state.conn, user.id).await?; - Ok(Json(UserListSchema::from(following_list))) -} - -#[utoipa::path( - get, - path = "/{username}/followers", - responses((status = 200, body = UserListSchema)) -)] -async fn get_user_followers( - State(state): State, - Path(username): Path, -) -> Result { - let user = get_user_by_username(&state.conn, &username) - .await? - .ok_or(UserError::NotFound)?; - let followers_list = get_followers(&state.conn, user.id).await?; - Ok(Json(UserListSchema::from(followers_list))) -} - -#[utoipa::path( - get, - path = "/all", - params(PaginationQuery), - responses( - (status = 200, description = "A public, paginated list of all users", body = PaginatedResponse) - ), - tag = "user" -)] -async fn get_all_users_public( - State(state): State, - Query(pagination): Query, -) -> Result { - let (users, total_items) = get_all_users(&state.conn, &pagination).await?; - - let page = pagination.page(); - let page_size = pagination.page_size(); - let total_pages = (total_items as f64 / page_size as f64).ceil() as u64; - - let response = PaginatedResponse { - items: users.into_iter().map(UserSchema::from).collect(), - page, - page_size, - total_pages, - total_items, - }; - - Ok(Json(response)) -} - -async fn get_all_users_count(State(state): State) -> Result { - let count = app::persistence::user::get_all_users_count(&state.conn).await?; - Ok(Json(json!({ "count": count }))) -} - -pub fn create_user_router() -> Router { - Router::new() - .route("/", get(users_get)) - .route("/all", get(get_all_users_public)) - .route("/count", get(get_all_users_count)) - .route("/me", get(get_me).put(update_me)) - .nest("/me/api-keys", create_api_key_router()) - .route("/{param}", get(get_user_by_param)) - .route("/{username}/thoughts", get(user_thoughts_get)) - .route("/{username}/followers", get(get_user_followers)) - .route("/{username}/following", get(get_user_following)) - .route( - "/{username}/follow", - post(user_follow_post).delete(user_follow_delete), - ) - .route("/{username}/inbox", post(user_inbox_post)) - .route("/{username}/outbox", get(user_outbox_get)) -} diff --git a/thoughts-backend/api/src/validation/mod.rs b/thoughts-backend/api/src/validation/mod.rs deleted file mode 100644 index f96bd85..0000000 --- a/thoughts-backend/api/src/validation/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod rejection; - -pub use rejection::ValidRejection; diff --git a/thoughts-backend/api/src/validation/rejection.rs b/thoughts-backend/api/src/validation/rejection.rs deleted file mode 100644 index 197d85f..0000000 --- a/thoughts-backend/api/src/validation/rejection.rs +++ /dev/null @@ -1,58 +0,0 @@ -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use validator::ValidationErrors; - -use crate::models::ValidationErrorResponse; - -#[derive(Debug)] -pub enum ValidationRejection { - Validator(V), // Validation errors - Extractor(E), // Extraction errors, e.g. axum's JsonRejection -} - -impl std::fmt::Display for ValidationRejection { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ValidationRejection::Validator(v) => write!(f, "{v}"), - ValidationRejection::Extractor(e) => write!(f, "{e}"), - } - } -} - -impl std::error::Error - for ValidationRejection -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ValidationRejection::Validator(v) => Some(v), - ValidationRejection::Extractor(e) => Some(e), - } - } -} - -impl IntoResponse - for ValidationRejection -{ - fn into_response(self) -> Response { - match self { - ValidationRejection::Validator(v) => { - tracing::error!("Validation error: {v}"); - ( - StatusCode::UNPROCESSABLE_ENTITY, - axum::Json(ValidationErrorResponse::from(v)), - ) - .into_response() - } - // logged by ApiError - ValidationRejection::Extractor(e) => e.into_response(), - } - } -} - -pub type ValidRejection = ValidationRejection; - -impl From for ValidRejection { - fn from(v: ValidationErrors) -> Self { - Self::Validator(v) - } -} diff --git a/thoughts-backend/app/Cargo.toml b/thoughts-backend/app/Cargo.toml deleted file mode 100644 index 09e0daa..0000000 --- a/thoughts-backend/app/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "app" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "app" -path = "src/lib.rs" - -[dependencies] -bcrypt = "0.17.1" -models = { path = "../models" } -validator = "0.20" -rand = "0.8.5" -sea-orm = { version = "1.1.12" } -chrono = { workspace = true } diff --git a/thoughts-backend/app/README.md b/thoughts-backend/app/README.md deleted file mode 100644 index 05b64a6..0000000 --- a/thoughts-backend/app/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# app - -No axum or api dependencies should be introduced into this folder. diff --git a/thoughts-backend/app/src/config.rs b/thoughts-backend/app/src/config.rs deleted file mode 100644 index 102edea..0000000 --- a/thoughts-backend/app/src/config.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub struct Config { - pub db_url: String, - pub host: String, - pub port: u32, - pub prefork: bool, - pub auth_secret: String, - pub base_url: String, -} - -impl Config { - pub fn from_env() -> Config { - Config { - db_url: std::env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"), - host: std::env::var("HOST").expect("HOST is not set in .env file"), - port: std::env::var("PORT") - .expect("PORT is not set in .env file") - .parse() - .expect("PORT is not a number"), - prefork: std::env::var("PREFORK").is_ok_and(|v| v == "1"), - auth_secret: std::env::var("AUTH_SECRET").expect("AUTH_SECRET is not set in .env file"), - base_url: std::env::var("BASE_URL").expect("BASE_URL is not set in .env file"), - } - } - - pub fn get_server_url(&self) -> String { - format!("{}:{}", self.host, self.port) - } -} diff --git a/thoughts-backend/app/src/error/mod.rs b/thoughts-backend/app/src/error/mod.rs deleted file mode 100644 index b3f1955..0000000 --- a/thoughts-backend/app/src/error/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod user; - -pub use user::UserError; diff --git a/thoughts-backend/app/src/error/user.rs b/thoughts-backend/app/src/error/user.rs deleted file mode 100644 index 67f435d0..0000000 --- a/thoughts-backend/app/src/error/user.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[derive(Debug)] -pub enum UserError { - NotFound, - NotFollowing, - Forbidden, - UsernameTaken, - AlreadyFollowing, - Validation(String), // Added Validation variant - Internal(String), -} - -impl std::fmt::Display for UserError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UserError::NotFound => write!(f, "User not found"), - UserError::NotFollowing => write!(f, "You are not following this user"), - UserError::Forbidden => write!(f, "You do not have permission to perform this action"), - UserError::UsernameTaken => write!(f, "Username is already taken"), - UserError::AlreadyFollowing => write!(f, "You are already following this user"), - UserError::Validation(msg) => write!(f, "Validation error: {}", msg), - UserError::Internal(msg) => write!(f, "Internal server error: {}", msg), - } - } -} - -impl std::error::Error for UserError {} diff --git a/thoughts-backend/app/src/lib.rs b/thoughts-backend/app/src/lib.rs deleted file mode 100644 index 51294a8..0000000 --- a/thoughts-backend/app/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod config; -pub mod error; -pub mod persistence; -pub mod state; diff --git a/thoughts-backend/app/src/persistence/api_key.rs b/thoughts-backend/app/src/persistence/api_key.rs deleted file mode 100644 index c9dcb25..0000000 --- a/thoughts-backend/app/src/persistence/api_key.rs +++ /dev/null @@ -1,93 +0,0 @@ -use bcrypt::{hash, verify, DEFAULT_COST}; -use models::domains::{api_key, user}; -use rand::distributions::{Alphanumeric, DistString}; -use sea_orm::{ - prelude::Uuid, ActiveModelTrait, ColumnTrait, DbConn, DbErr, EntityTrait, QueryFilter, Set, -}; - -use crate::error::UserError; - -const KEY_PREFIX: &str = "th_"; -const KEY_RANDOM_LENGTH: usize = 32; -const KEY_LOOKUP_PREFIX_LENGTH: usize = 8; - -fn generate_key() -> String { - let random_part = Alphanumeric.sample_string(&mut rand::thread_rng(), KEY_RANDOM_LENGTH); - format!("{}{}", KEY_PREFIX, random_part) -} - -pub async fn create_api_key( - db: &DbConn, - user_id: Uuid, - name: String, -) -> Result<(api_key::Model, String), UserError> { - let plaintext_key = generate_key(); - let key_hash = - hash(&plaintext_key, DEFAULT_COST).map_err(|e| UserError::Internal(e.to_string()))?; - let key_prefix = plaintext_key[..KEY_LOOKUP_PREFIX_LENGTH].to_string(); - - let new_key = api_key::ActiveModel { - user_id: Set(user_id), - name: Set(name), - key_hash: Set(key_hash), - key_prefix: Set(key_prefix), - ..Default::default() - } - .insert(db) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - Ok((new_key, plaintext_key)) -} - -pub async fn validate_api_key(db: &DbConn, plaintext_key: &str) -> Result { - if !plaintext_key.starts_with(KEY_PREFIX) - || plaintext_key.len() != KEY_PREFIX.len() + KEY_RANDOM_LENGTH - { - return Err(UserError::Validation("Invalid API key format".to_string())); - } - - let key_prefix = &plaintext_key[..KEY_LOOKUP_PREFIX_LENGTH]; - - let candidate_keys = api_key::Entity::find() - .filter(api_key::Column::KeyPrefix.eq(key_prefix)) - .all(db) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - for key in candidate_keys { - if verify(plaintext_key, &key.key_hash).unwrap_or(false) { - return super::user::get_user(db, key.user_id) - .await - .map_err(|e| UserError::Internal(e.to_string()))? - .ok_or(UserError::NotFound); - } - } - - Err(UserError::Validation("Invalid API key".to_string())) -} - -pub async fn get_api_keys_for_user( - db: &DbConn, - user_id: Uuid, -) -> Result, DbErr> { - api_key::Entity::find() - .filter(api_key::Column::UserId.eq(user_id)) - .all(db) - .await -} - -pub async fn delete_api_key(db: &DbConn, key_id: Uuid, user_id: Uuid) -> Result<(), UserError> { - let result = api_key::Entity::delete_many() - .filter(api_key::Column::Id.eq(key_id)) - .filter(api_key::Column::UserId.eq(user_id)) // Ensure user owns the key - .exec(db) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - if result.rows_affected == 0 { - Err(UserError::NotFound) - } else { - Ok(()) - } -} diff --git a/thoughts-backend/app/src/persistence/auth.rs b/thoughts-backend/app/src/persistence/auth.rs deleted file mode 100644 index dad2716..0000000 --- a/thoughts-backend/app/src/persistence/auth.rs +++ /dev/null @@ -1,55 +0,0 @@ -use bcrypt::{hash, verify, BcryptError, DEFAULT_COST}; -use models::{ - domains::user, - params::auth::{LoginParams, RegisterParams}, -}; -use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, EntityTrait, QueryFilter, Set}; -use validator::Validate; // Import the Validate trait - -use crate::error::UserError; - -fn hash_password(password: &str) -> Result { - hash(password, DEFAULT_COST) -} - -pub async fn register_user(db: &DbConn, params: RegisterParams) -> Result { - params - .validate() - .map_err(|e| UserError::Validation(e.to_string()))?; - - let hashed_password = - hash_password(¶ms.password).map_err(|e| UserError::Internal(e.to_string()))?; - - let new_user = user::ActiveModel { - username: Set(params.username.clone()), - password_hash: Set(Some(hashed_password)), - email: Set(Some(params.email)), - display_name: Set(Some(params.username)), - ..Default::default() - }; - - new_user.insert(db).await.map_err(|e| { - if let Some(sea_orm::SqlErr::UniqueConstraintViolation { .. }) = e.sql_err() { - UserError::UsernameTaken - } else { - UserError::Internal(e.to_string()) - } - }) -} - -pub async fn authenticate_user(db: &DbConn, params: LoginParams) -> Result { - let user = user::Entity::find() - .filter(user::Column::Username.eq(params.username)) - .one(db) - .await - .map_err(|e| UserError::Internal(e.to_string()))? - .ok_or(UserError::NotFound)?; - - let password_hash = user.password_hash.as_ref().ok_or(UserError::NotFound)?; - - if verify(params.password, password_hash).map_err(|e| UserError::Internal(e.to_string()))? { - Ok(user) - } else { - Err(UserError::NotFound) - } -} diff --git a/thoughts-backend/app/src/persistence/follow.rs b/thoughts-backend/app/src/persistence/follow.rs deleted file mode 100644 index cfca09e..0000000 --- a/thoughts-backend/app/src/persistence/follow.rs +++ /dev/null @@ -1,91 +0,0 @@ -use sea_orm::{ - prelude::Uuid, ActiveModelTrait, ColumnTrait, DbConn, DbErr, EntityTrait, QueryFilter, Set, -}; - -use crate::{error::UserError, persistence::user::get_user_by_username}; -use models::domains::follow; - -pub async fn add_follower( - db: &DbConn, - following_id: Uuid, - follower_actor_id: &str, -) -> Result<(), UserError> { - let follower_username = follower_actor_id - .split('/') - .last() - .ok_or_else(|| UserError::Internal("Invalid follower actor ID".to_string()))?; - - let follower = get_user_by_username(db, follower_username) - .await - .map_err(|e| UserError::Internal(e.to_string()))? - .ok_or(UserError::NotFound)?; - - follow_user(db, follower.id, following_id) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - Ok(()) -} - -pub async fn follow_user(db: &DbConn, follower_id: Uuid, following_id: Uuid) -> Result<(), DbErr> { - if follower_id == following_id { - return Err(DbErr::Custom("Users cannot follow themselves".to_string())); - } - - let follow = follow::ActiveModel { - follower_id: Set(follower_id), - following_id: Set(following_id), - }; - - follow.insert(db).await?; - Ok(()) -} - -pub async fn unfollow_user( - db: &DbConn, - follower_id: Uuid, - following_id: Uuid, -) -> Result<(), UserError> { - let deleted_result = follow::Entity::delete_many() - .filter(follow::Column::FollowerId.eq(follower_id)) - .filter(follow::Column::FollowingId.eq(following_id)) - .exec(db) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - if deleted_result.rows_affected == 0 { - return Err(UserError::NotFollowing); - } - - Ok(()) -} - -pub async fn get_following_ids(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let followed_users = follow::Entity::find() - .filter(follow::Column::FollowerId.eq(user_id)) - .all(db) - .await?; - - Ok(followed_users.into_iter().map(|f| f.following_id).collect()) -} - -pub async fn get_follower_ids(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let followers = follow::Entity::find() - .filter(follow::Column::FollowingId.eq(user_id)) - .all(db) - .await?; - Ok(followers.into_iter().map(|f| f.follower_id).collect()) -} - -pub async fn get_friend_ids(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let following = get_following_ids(db, user_id).await?; - let followers = get_follower_ids(db, user_id).await?; - - let following_set: std::collections::HashSet = following.into_iter().collect(); - let followers_set: std::collections::HashSet = followers.into_iter().collect(); - - Ok(following_set - .intersection(&followers_set) - .cloned() - .collect()) -} diff --git a/thoughts-backend/app/src/persistence/mod.rs b/thoughts-backend/app/src/persistence/mod.rs deleted file mode 100644 index 174ce76..0000000 --- a/thoughts-backend/app/src/persistence/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod api_key; -pub mod auth; -pub mod follow; -pub mod search; -pub mod tag; -pub mod thought; -pub mod user; diff --git a/thoughts-backend/app/src/persistence/search.rs b/thoughts-backend/app/src/persistence/search.rs deleted file mode 100644 index 16c0745..0000000 --- a/thoughts-backend/app/src/persistence/search.rs +++ /dev/null @@ -1,66 +0,0 @@ -use models::{ - domains::{thought, user}, - schemas::thought::ThoughtWithAuthor, -}; -use sea_orm::{ - prelude::{Expr, Uuid}, - DatabaseConnection, DbErr, EntityTrait, JoinType, QueryFilter, QuerySelect, RelationTrait, - Value, -}; - -use crate::persistence::follow; - -fn is_visible( - author_id: Uuid, - viewer_id: Option, - friend_ids: &[Uuid], - visibility: &thought::Visibility, -) -> bool { - match visibility { - thought::Visibility::Public => true, - thought::Visibility::Private => viewer_id.map_or(false, |v| v == author_id), - thought::Visibility::FriendsOnly => { - viewer_id.map_or(false, |v| v == author_id || friend_ids.contains(&author_id)) - } - } -} - -pub async fn search_thoughts( - db: &DatabaseConnection, - query: &str, - viewer_id: Option, -) -> Result, DbErr> { - let mut friend_ids = Vec::new(); - if let Some(viewer) = viewer_id { - friend_ids = follow::get_friend_ids(db, viewer).await?; - } - - // We must join with the user table to get the author's username - let thoughts_with_authors = thought::Entity::find() - .column_as(user::Column::Username, "author_username") - .column_as(user::Column::DisplayName, "author_display_name") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .filter(Expr::cust_with_values( - "thought.search_document @@ websearch_to_tsquery('english', $1)", - [Value::from(query)], - )) - .into_model::() // Convert directly in the query - .all(db) - .await?; - - // Apply visibility filtering in Rust after the search - Ok(thoughts_with_authors - .into_iter() - .filter(|t| is_visible(t.author_id, viewer_id, &friend_ids, &t.visibility)) - .collect()) -} - -pub async fn search_users(db: &DatabaseConnection, query: &str) -> Result, DbErr> { - user::Entity::find() - .filter(Expr::cust_with_values( - "\"user\".search_document @@ websearch_to_tsquery('english', $1)", - [Value::from(query)], - )) - .all(db) - .await -} diff --git a/thoughts-backend/app/src/persistence/tag.rs b/thoughts-backend/app/src/persistence/tag.rs deleted file mode 100644 index 79f03a5..0000000 --- a/thoughts-backend/app/src/persistence/tag.rs +++ /dev/null @@ -1,120 +0,0 @@ -use chrono::{Duration, Utc}; -use models::domains::{tag, thought, thought_tag}; -use sea_orm::{ - prelude::Expr, sea_query::Alias, sqlx::types::uuid, ColumnTrait, ConnectionTrait, DbErr, - EntityTrait, QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set, -}; -use std::collections::HashSet; - -pub fn parse_hashtags(content: &str) -> Vec { - content - .split_whitespace() - .filter_map(|word| { - if word.starts_with('#') && word.len() > 1 { - Some(word[1..].to_lowercase().to_string()) - } else { - None - } - }) - .collect::>() - .into_iter() - .collect() -} - -pub async fn find_or_create_tags(db: &C, names: Vec) -> Result, DbErr> -where - C: ConnectionTrait, -{ - if names.is_empty() { - return Ok(vec![]); - } - let existing_tags = tag::Entity::find() - .filter(tag::Column::Name.is_in(names.clone())) - .all(db) - .await?; - - let existing_names: HashSet = existing_tags.iter().map(|t| t.name.clone()).collect(); - let new_names: Vec = names - .into_iter() - .filter(|n| !existing_names.contains(n)) - .collect(); - - if !new_names.is_empty() { - let new_tags: Vec = new_names - .clone() - .into_iter() - .map(|name| tag::ActiveModel { - name: Set(name), - ..Default::default() - }) - .collect(); - tag::Entity::insert_many(new_tags).exec(db).await?; - } - - tag::Entity::find() - .filter( - tag::Column::Name.is_in( - existing_names - .union(&new_names.into_iter().collect()) - .cloned() - .collect::>(), - ), - ) - .all(db) - .await -} - -pub async fn link_tags_to_thought( - db: &C, - thought_id: uuid::Uuid, - tags: Vec, -) -> Result<(), DbErr> -where - C: ConnectionTrait, -{ - if tags.is_empty() { - return Ok(()); - } - let links: Vec = tags - .into_iter() - .map(|tag| thought_tag::ActiveModel { - thought_id: Set(thought_id), - tag_id: Set(tag.id), - }) - .collect(); - - thought_tag::Entity::insert_many(links).exec(db).await?; - Ok(()) -} - -pub async fn get_popular_tags(db: &C) -> Result, DbErr> -where - C: ConnectionTrait, -{ - let seven_days_ago = Utc::now() - Duration::days(7); - - let popular_tags = tag::Entity::find() - .select_only() - .column(tag::Column::Name) - .column_as(Expr::col((tag::Entity, tag::Column::Id)).count(), "count") - .join( - sea_orm::JoinType::InnerJoin, - tag::Relation::ThoughtTag.def(), - ) - .join( - sea_orm::JoinType::InnerJoin, - thought_tag::Relation::Thought.def(), - ) - .filter(thought::Column::CreatedAt.gte(seven_days_ago)) - .filter(thought::Column::Visibility.eq(thought::Visibility::Public)) - .group_by(tag::Column::Name) - .group_by(tag::Column::Id) - .order_by_desc(Expr::col(Alias::new("count"))) - .order_by_asc(tag::Column::Name) - .limit(10) - .into_tuple::<(String, i64)>() - .all(db) - .await?; - - Ok(popular_tags.into_iter().map(|(name, _)| name).collect()) -} diff --git a/thoughts-backend/app/src/persistence/thought.rs b/thoughts-backend/app/src/persistence/thought.rs deleted file mode 100644 index 0da44e2..0000000 --- a/thoughts-backend/app/src/persistence/thought.rs +++ /dev/null @@ -1,386 +0,0 @@ -use sea_orm::{ - prelude::Uuid, sea_query::SimpleExpr, ActiveModelTrait, ColumnTrait, Condition, DbConn, DbErr, - EntityTrait, JoinType, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, RelationTrait, - Set, TransactionTrait, -}; - -use models::{ - domains::{tag, thought, thought_tag, user}, - params::thought::CreateThoughtParams, - queries::pagination::PaginationQuery, - schemas::thought::{ThoughtSchema, ThoughtThreadSchema, ThoughtWithAuthor}, -}; - -use crate::{ - error::UserError, - persistence::{ - follow, - tag::{find_or_create_tags, link_tags_to_thought, parse_hashtags}, - }, -}; - -pub async fn create_thought( - db: &DbConn, - author_id: Uuid, - params: CreateThoughtParams, -) -> Result { - let txn = db.begin().await?; - - let new_thought = thought::ActiveModel { - author_id: Set(author_id), - content: Set(params.content.clone()), - reply_to_id: Set(params.reply_to_id), - visibility: Set(params.visibility.unwrap_or(thought::Visibility::Public)), - ..Default::default() - } - .insert(&txn) - .await?; - - if new_thought.visibility == thought::Visibility::Public { - let tag_names = parse_hashtags(¶ms.content); - if !tag_names.is_empty() { - let tags = find_or_create_tags(&txn, tag_names).await?; - link_tags_to_thought(&txn, new_thought.id, tags).await?; - } - } - - txn.commit().await?; - Ok(new_thought) -} - -pub async fn get_thought( - db: &DbConn, - thought_id: Uuid, - viewer_id: Option, -) -> Result, DbErr> { - let thought = thought::Entity::find_by_id(thought_id).one(db).await?; - - match thought { - Some(t) => { - if t.visibility == thought::Visibility::Public { - return Ok(Some(t)); - } - - if let Some(viewer) = viewer_id { - if t.author_id == viewer { - return Ok(Some(t)); - } - - if t.visibility == thought::Visibility::FriendsOnly { - let author_friends = follow::get_friend_ids(db, t.author_id).await?; - if author_friends.contains(&viewer) { - return Ok(Some(t)); - } - } - } - - Ok(None) - } - None => Ok(None), - } -} - -pub async fn delete_thought(db: &DbConn, thought_id: Uuid) -> Result<(), DbErr> { - thought::Entity::delete_by_id(thought_id).exec(db).await?; - Ok(()) -} - -pub async fn get_thoughts_by_user( - db: &DbConn, - user_id: Uuid, - viewer_id: Option, -) -> Result, DbErr> { - let mut friend_ids = vec![]; - if let Some(viewer) = viewer_id { - friend_ids = follow::get_friend_ids(db, viewer).await?; - } - - thought::Entity::find() - .select_only() - .column(thought::Column::Id) - .column(thought::Column::Content) - .column(thought::Column::ReplyToId) - .column(thought::Column::CreatedAt) - .column(thought::Column::AuthorId) - .column(thought::Column::Visibility) - .column_as(user::Column::DisplayName, "author_display_name") - .column_as(user::Column::Username, "author_username") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .filter(apply_visibility_filter(user_id, viewer_id, &friend_ids)) - .filter(thought::Column::AuthorId.eq(user_id)) - .order_by_desc(thought::Column::CreatedAt) - .into_model::() - .all(db) - .await -} - -pub async fn get_feed_for_user( - db: &DbConn, - following_ids: Vec, - viewer_id: Option, -) -> Result, UserError> { - if following_ids.is_empty() { - return Ok(vec![]); - } - - let mut friend_ids = vec![]; - if let Some(viewer) = viewer_id { - friend_ids = follow::get_friend_ids(db, viewer) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - } - - thought::Entity::find() - .select_only() - .column(thought::Column::Id) - .column(thought::Column::Content) - .column(thought::Column::ReplyToId) - .column(thought::Column::CreatedAt) - .column(thought::Column::Visibility) - .column(thought::Column::AuthorId) - .column_as(user::Column::Username, "author_username") - .column_as(user::Column::DisplayName, "author_display_name") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .filter( - Condition::any().add(following_ids.iter().fold( - Condition::all(), - |cond, &author_id| { - cond.add(apply_visibility_filter(author_id, viewer_id, &friend_ids)) - }, - )), - ) - .filter(thought::Column::AuthorId.is_in(following_ids)) - .order_by_desc(thought::Column::CreatedAt) - .into_model::() - .all(db) - .await - .map_err(|e| UserError::Internal(e.to_string())) -} - -pub async fn get_feed_for_users_and_self( - db: &DbConn, - user_id: Uuid, - following_ids: Vec, -) -> Result, DbErr> { - let mut authors_to_include = following_ids; - authors_to_include.push(user_id); - - thought::Entity::find() - .select_only() - .column(thought::Column::Id) - .column(thought::Column::Content) - .column(thought::Column::ReplyToId) - .column(thought::Column::CreatedAt) - .column(thought::Column::Visibility) - .column(thought::Column::AuthorId) - .column_as(user::Column::Username, "author_username") - .column_as(user::Column::DisplayName, "author_display_name") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .filter(thought::Column::AuthorId.is_in(authors_to_include)) - .filter( - Condition::any() - .add(thought::Column::Visibility.eq(thought::Visibility::Public)) - .add(thought::Column::Visibility.eq(thought::Visibility::FriendsOnly)), - ) - .order_by_desc(thought::Column::CreatedAt) - .into_model::() - .all(db) - .await -} - -pub async fn get_feed_for_users_and_self_paginated( - db: &DbConn, - user_id: Uuid, - following_ids: Vec, - pagination: &PaginationQuery, -) -> Result<(Vec, u64), DbErr> { - let mut authors_to_include = following_ids; - authors_to_include.push(user_id); - - let paginator = thought::Entity::find() - .select_only() - .column(thought::Column::Id) - .column(thought::Column::Content) - .column(thought::Column::ReplyToId) - .column(thought::Column::CreatedAt) - .column(thought::Column::Visibility) - .column(thought::Column::AuthorId) - .column_as(user::Column::Username, "author_username") - .column_as(user::Column::DisplayName, "author_display_name") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .filter(thought::Column::AuthorId.is_in(authors_to_include)) - .filter( - Condition::any() - .add(thought::Column::Visibility.eq(thought::Visibility::Public)) - .add(thought::Column::Visibility.eq(thought::Visibility::FriendsOnly)), - ) - .order_by_desc(thought::Column::CreatedAt) - .into_model::() - .paginate(db, pagination.page_size()); - - let total_items = paginator.num_items().await?; - let thoughts = paginator.fetch_page(pagination.page() - 1).await?; - - Ok((thoughts, total_items)) -} - -pub async fn get_thoughts_by_tag_name( - db: &DbConn, - tag_name: &str, - viewer_id: Option, -) -> Result, DbErr> { - let mut friend_ids = Vec::new(); - if let Some(viewer) = viewer_id { - friend_ids = follow::get_friend_ids(db, viewer).await?; - } - - let thoughts = thought::Entity::find() - .select_only() - .column(thought::Column::Id) - .column(thought::Column::Content) - .column(thought::Column::ReplyToId) - .column(thought::Column::CreatedAt) - .column(thought::Column::AuthorId) - .column(thought::Column::Visibility) - .column_as(user::Column::Username, "author_username") - .column_as(user::Column::DisplayName, "author_display_name") - .join(JoinType::InnerJoin, thought::Relation::User.def()) - .join(JoinType::InnerJoin, thought::Relation::ThoughtTag.def()) - .join(JoinType::InnerJoin, thought_tag::Relation::Tag.def()) - .filter(tag::Column::Name.eq(tag_name.to_lowercase())) - .order_by_desc(thought::Column::CreatedAt) - .into_model::() - .all(db) - .await?; - - let visible_thoughts = thoughts - .into_iter() - .filter(|thought| { - let mut condition = thought.visibility == thought::Visibility::Public; - if let Some(viewer) = viewer_id { - if thought.author_id == viewer { - condition = true; - } - if thought.visibility == thought::Visibility::FriendsOnly - && friend_ids.contains(&thought.author_id) - { - condition = true; - } - } - condition - }) - .collect(); - - Ok(visible_thoughts) -} - -pub fn apply_visibility_filter( - user_id: Uuid, - viewer_id: Option, - friend_ids: &[Uuid], -) -> SimpleExpr { - let mut condition = - Condition::any().add(thought::Column::Visibility.eq(thought::Visibility::Public)); - - if let Some(viewer) = viewer_id { - if user_id == viewer { - condition = condition - .add(thought::Column::Visibility.eq(thought::Visibility::FriendsOnly)) - .add(thought::Column::Visibility.eq(thought::Visibility::Private)); - } else if !friend_ids.is_empty() && friend_ids.contains(&user_id) { - condition = - condition.add(thought::Column::Visibility.eq(thought::Visibility::FriendsOnly)); - } - } - condition.into() -} - -pub async fn get_thought_with_replies( - db: &DbConn, - thought_id: Uuid, - viewer_id: Option, -) -> Result, DbErr> { - let root_thought = match get_thought(db, thought_id, viewer_id).await? { - Some(t) => t, - None => return Ok(None), - }; - - let mut all_thoughts_in_thread = vec![root_thought.clone()]; - let mut ids_to_fetch = vec![root_thought.id]; - let mut friend_ids = vec![]; - if let Some(viewer) = viewer_id { - friend_ids = follow::get_friend_ids(db, viewer).await?; - } - - while !ids_to_fetch.is_empty() { - let replies = thought::Entity::find() - .filter(thought::Column::ReplyToId.is_in(ids_to_fetch)) - .all(db) - .await?; - - if replies.is_empty() { - break; - } - - ids_to_fetch = replies.iter().map(|r| r.id).collect(); - all_thoughts_in_thread.extend(replies); - } - - let mut thought_schemas = vec![]; - for thought in all_thoughts_in_thread { - if let Some(author) = user::Entity::find_by_id(thought.author_id).one(db).await? { - let is_visible = match thought.visibility { - thought::Visibility::Public => true, - thought::Visibility::Private => viewer_id.map_or(false, |v| v == thought.author_id), - thought::Visibility::FriendsOnly => viewer_id.map_or(false, |v| { - v == thought.author_id || friend_ids.contains(&thought.author_id) - }), - }; - - if is_visible { - thought_schemas.push(ThoughtSchema::from_models(&thought, &author)); - } - } - } - - fn build_thread( - thought_id: Uuid, - schemas_map: &std::collections::HashMap, - replies_map: &std::collections::HashMap>, - ) -> Option { - schemas_map.get(&thought_id).map(|thought_schema| { - let replies = replies_map - .get(&thought_id) - .unwrap_or(&vec![]) - .iter() - .filter_map(|reply_id| build_thread(*reply_id, schemas_map, replies_map)) - .collect(); - - ThoughtThreadSchema { - id: thought_schema.id, - author_username: thought_schema.author_username.clone(), - author_display_name: thought_schema.author_display_name.clone(), - content: thought_schema.content.clone(), - visibility: thought_schema.visibility.clone(), - reply_to_id: thought_schema.reply_to_id, - created_at: thought_schema.created_at.clone(), - replies, - } - }) - } - - let schemas_map: std::collections::HashMap = - thought_schemas.into_iter().map(|s| (s.id, s)).collect(); - - let mut replies_map: std::collections::HashMap> = - std::collections::HashMap::new(); - for thought in schemas_map.values() { - if let Some(parent_id) = thought.reply_to_id { - if schemas_map.contains_key(&parent_id) { - replies_map.entry(parent_id).or_default().push(thought.id); - } - } - } - - Ok(build_thread(root_thought.id, &schemas_map, &replies_map)) -} diff --git a/thoughts-backend/app/src/persistence/user.rs b/thoughts-backend/app/src/persistence/user.rs deleted file mode 100644 index ab0de03..0000000 --- a/thoughts-backend/app/src/persistence/user.rs +++ /dev/null @@ -1,186 +0,0 @@ -use models::queries::pagination::PaginationQuery; -use sea_orm::prelude::Uuid; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DbConn, DbErr, EntityTrait, JoinType, PaginatorTrait, - QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set, TransactionTrait, -}; - -use models::domains::{top_friends, user}; -use models::params::user::{CreateUserParams, UpdateUserParams}; -use models::queries::user::UserQuery; - -use crate::error::UserError; -use crate::persistence::follow::{get_follower_ids, get_following_ids, get_friend_ids}; - -pub async fn create_user( - db: &DbConn, - params: CreateUserParams, -) -> Result { - user::ActiveModel { - username: Set(params.username), - ..Default::default() - } - .save(db) - .await -} - -pub async fn search_users(db: &DbConn, query: UserQuery) -> Result, DbErr> { - user::Entity::find() - .filter(user::Column::Username.contains(query.username.unwrap_or_default())) - .all(db) - .await -} - -pub async fn get_user(db: &DbConn, id: Uuid) -> Result, DbErr> { - user::Entity::find_by_id(id).one(db).await -} - -pub async fn get_user_by_username( - db: &DbConn, - username: &str, -) -> Result, DbErr> { - user::Entity::find() - .filter(user::Column::Username.eq(username)) - .one(db) - .await -} - -pub async fn get_users_by_ids(db: &DbConn, ids: Vec) -> Result, DbErr> { - user::Entity::find() - .filter(user::Column::Id.is_in(ids)) - .all(db) - .await -} - -pub async fn update_user_profile( - db: &DbConn, - user_id: Uuid, - params: UpdateUserParams, -) -> Result { - let mut user: user::ActiveModel = get_user(db, user_id) - .await - .map_err(|e| UserError::Internal(e.to_string()))? - .ok_or(UserError::NotFound)? - .into(); - - if let Some(display_name) = params.display_name { - user.display_name = Set(Some(display_name)); - } - if let Some(bio) = params.bio { - user.bio = Set(Some(bio)); - } - if let Some(avatar_url) = params.avatar_url { - user.avatar_url = Set(Some(avatar_url)); - } - if let Some(header_url) = params.header_url { - user.header_url = Set(Some(header_url)); - } - if let Some(custom_css) = params.custom_css { - user.custom_css = Set(Some(custom_css)); - } - - if let Some(friend_usernames) = params.top_friends { - let txn = db - .begin() - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - top_friends::Entity::delete_many() - .filter(top_friends::Column::UserId.eq(user_id)) - .exec(&txn) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - let friends = user::Entity::find() - .filter(user::Column::Username.is_in(friend_usernames.clone())) - .all(&txn) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - - if friends.len() != friend_usernames.len() { - return Err(UserError::Validation( - "One or more usernames in top_friends do not exist".to_string(), - )); - } - - let new_top_friends: Vec = friends - .iter() - .enumerate() - .map(|(index, friend)| top_friends::ActiveModel { - user_id: Set(user_id), - friend_id: Set(friend.id), - position: Set((index + 1) as i16), - ..Default::default() - }) - .collect(); - - if !new_top_friends.is_empty() { - top_friends::Entity::insert_many(new_top_friends) - .exec(&txn) - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - } - - txn.commit() - .await - .map_err(|e| UserError::Internal(e.to_string()))?; - } - - user.update(db) - .await - .map_err(|e| UserError::Internal(e.to_string())) -} - -pub async fn get_top_friends(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - user::Entity::find() - .join( - JoinType::InnerJoin, - top_friends::Relation::Friend.def().rev(), - ) - .filter(top_friends::Column::UserId.eq(user_id)) - .order_by_asc(top_friends::Column::Position) - .all(db) - .await -} - -pub async fn get_friends(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let friend_ids = get_friend_ids(db, user_id).await?; - if friend_ids.is_empty() { - return Ok(vec![]); - } - get_users_by_ids(db, friend_ids).await -} - -pub async fn get_following(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let following_ids = get_following_ids(db, user_id).await?; - if following_ids.is_empty() { - return Ok(vec![]); - } - get_users_by_ids(db, following_ids).await -} - -pub async fn get_followers(db: &DbConn, user_id: Uuid) -> Result, DbErr> { - let follower_ids = get_follower_ids(db, user_id).await?; - if follower_ids.is_empty() { - return Ok(vec![]); - } - get_users_by_ids(db, follower_ids).await -} - -pub async fn get_all_users( - db: &DbConn, - pagination: &PaginationQuery, -) -> Result<(Vec, u64), DbErr> { - let paginator = user::Entity::find() - .order_by_desc(user::Column::CreatedAt) - .paginate(db, pagination.page_size()); - - let total_items = paginator.num_items().await?; - let users = paginator.fetch_page(pagination.page() - 1).await?; - - Ok((users, total_items)) -} - -pub async fn get_all_users_count(db: &DbConn) -> Result { - user::Entity::find().count(db).await -} diff --git a/thoughts-backend/app/src/state.rs b/thoughts-backend/app/src/state.rs deleted file mode 100644 index 26af328..0000000 --- a/thoughts-backend/app/src/state.rs +++ /dev/null @@ -1,7 +0,0 @@ -use sea_orm::DatabaseConnection; - -#[derive(Clone)] -pub struct AppState { - pub conn: DatabaseConnection, - pub base_url: String, -} diff --git a/thoughts-backend/common/Cargo.toml b/thoughts-backend/common/Cargo.toml deleted file mode 100644 index e28a054..0000000 --- a/thoughts-backend/common/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "common" -version = "0.1.0" -edition = "2021" - -[lib] -name = "common" -path = "src/lib.rs" - -[dependencies] -serde = { workspace = true } -utoipa = { workspace = true } -sea-orm = { workspace = true } -sea-query = { workspace = true } diff --git a/thoughts-backend/common/src/lib.rs b/thoughts-backend/common/src/lib.rs deleted file mode 100644 index 9a08c4a..0000000 --- a/thoughts-backend/common/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -use sea_orm::prelude::DateTimeWithTimeZone; -use sea_orm::TryGetError; -use sea_orm::{sea_query::ColumnType, sea_query::Value, sea_query::ValueType, TryGetable}; -use sea_query::ValueTypeErr; -use serde::Serialize; -use utoipa::ToSchema; - -#[derive(Serialize, ToSchema, Debug, Clone)] -#[schema(example = "2025-09-05T12:34:56Z")] -pub struct DateTimeWithTimeZoneWrapper(String); - -impl From for DateTimeWithTimeZoneWrapper { - fn from(value: DateTimeWithTimeZone) -> Self { - DateTimeWithTimeZoneWrapper(value.to_rfc3339()) - } -} - -impl TryGetable for DateTimeWithTimeZoneWrapper { - fn try_get_by( - res: &sea_orm::QueryResult, - index: I, - ) -> Result { - let value: String = res.try_get_by(index)?; - Ok(DateTimeWithTimeZoneWrapper(value)) - } - - fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result { - let value: String = res.try_get(pre, col)?; - Ok(DateTimeWithTimeZoneWrapper(value)) - } -} - -impl ValueType for DateTimeWithTimeZoneWrapper { - fn try_from(v: Value) -> Result { - if let Value::String(Some(string)) = v { - Ok(DateTimeWithTimeZoneWrapper(*string)) - } else { - Err(ValueTypeErr) - } - } - - fn array_type() -> sea_query::ArrayType { - sea_query::ArrayType::String - } - - fn column_type() -> ColumnType { - ColumnType::String(sea_query::StringLen::Max) - } - - fn type_name() -> String { - "DateTimeWithTimeZoneWrapper".to_string() - } -} diff --git a/thoughts-backend/doc/Cargo.toml b/thoughts-backend/doc/Cargo.toml deleted file mode 100644 index dd03f55..0000000 --- a/thoughts-backend/doc/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "doc" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "doc" -path = "src/lib.rs" - -[dependencies] -axum = { workspace = true } -tracing = { workspace = true } -utoipa = { workspace = true, features = ["axum_extras"] } -utoipa-swagger-ui = { version = "9.0.2", features = [ - "axum", - "vendored", -], default-features = false } -utoipa-scalar = { version = "0.3.0", features = [ - "axum", -], default-features = false } - -# api = { path = "../api" } -models = { path = "../models" } diff --git a/thoughts-backend/doc/src/api_key.rs b/thoughts-backend/doc/src/api_key.rs deleted file mode 100644 index 669b2f4..0000000 --- a/thoughts-backend/doc/src/api_key.rs +++ /dev/null @@ -1,16 +0,0 @@ -use api::{models::ApiErrorResponse, routers::api_key::*}; -use models::schemas::api_key::{ApiKeyListSchema, ApiKeyRequest, ApiKeyResponse, ApiKeySchema}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(get_keys, create_key, delete_key), - components(schemas( - ApiKeySchema, - ApiKeyListSchema, - ApiKeyRequest, - ApiKeyResponse, - ApiErrorResponse, - )) -)] -pub(super) struct ApiKeyApi; diff --git a/thoughts-backend/doc/src/auth.rs b/thoughts-backend/doc/src/auth.rs deleted file mode 100644 index ae3129c..0000000 --- a/thoughts-backend/doc/src/auth.rs +++ /dev/null @@ -1,23 +0,0 @@ -use api::{ - models::{ApiErrorResponse, ParamsErrorResponse}, - routers::auth::*, -}; -use models::{ - params::auth::{LoginParams, RegisterParams}, - schemas::user::UserSchema, -}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(register, login), - components(schemas( - RegisterParams, - LoginParams, - UserSchema, - TokenResponse, - ApiErrorResponse, - ParamsErrorResponse, - )) -)] -pub(super) struct AuthApi; diff --git a/thoughts-backend/doc/src/feed.rs b/thoughts-backend/doc/src/feed.rs deleted file mode 100644 index 3e70582..0000000 --- a/thoughts-backend/doc/src/feed.rs +++ /dev/null @@ -1,10 +0,0 @@ -use api::{models::ApiErrorResponse, routers::feed::*}; -use models::schemas::thought::{ThoughtListSchema, ThoughtSchema}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(feed_get), - components(schemas(ThoughtSchema, ThoughtListSchema, ApiErrorResponse)) -)] -pub(super) struct FeedApi; diff --git a/thoughts-backend/doc/src/friends.rs b/thoughts-backend/doc/src/friends.rs deleted file mode 100644 index 1870577..0000000 --- a/thoughts-backend/doc/src/friends.rs +++ /dev/null @@ -1,12 +0,0 @@ -use utoipa::OpenApi; - -use api::models::{ApiErrorResponse, ParamsErrorResponse}; -use api::routers::friends::*; -use models::schemas::user::{UserListSchema, UserSchema}; - -#[derive(OpenApi)] -#[openapi( - paths(get_friends_list,), - components(schemas(UserListSchema, ApiErrorResponse, ParamsErrorResponse, UserSchema)) -)] -pub(super) struct FriendsApi; diff --git a/thoughts-backend/doc/src/lib.rs b/thoughts-backend/doc/src/lib.rs deleted file mode 100644 index 01ee9bc..0000000 --- a/thoughts-backend/doc/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -use axum::Router; -use utoipa::{ - openapi::security::{ApiKey, ApiKeyValue, Http, SecurityScheme}, - Modify, OpenApi, -}; -use utoipa_scalar::{Scalar, Servable as ScalarServable}; -use utoipa_swagger_ui::SwaggerUi; - -#[derive(OpenApi)] -#[openapi( - tags( - (name = "root", description = "Root API"), - (name = "auth", description = "Authentication API"), - (name = "user", description = "User & Social API"), - (name = "thought", description = "Thoughts API"), - (name = "feed", description = "Feed API"), - (name = "tag", description = "Tag Discovery API"), - (name = "friends", description = "Friends API"), - (name = "search", description = "Search API"), - ), - modifiers(&SecurityAddon), -)] -struct _ApiDoc; - -struct SecurityAddon; -impl Modify for SecurityAddon { - fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { - let components = openapi.components.get_or_insert_with(Default::default); - components.add_security_scheme( - "bearer_auth", - SecurityScheme::Http(Http::new(utoipa::openapi::security::HttpAuthScheme::Bearer)), - ); - components.add_security_scheme( - "api_key", - SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("Authorization"))), - ); - } -} - -pub trait ApiDocExt { - fn attach_doc(self) -> Self; -} - -impl ApiDocExt for Router { - fn attach_doc(self) -> Self { - tracing::info!("Attaching API documentation"); - - self.merge(SwaggerUi::new("/docs").url("/openapi.json", _ApiDoc::openapi())) - .merge(Scalar::with_url("/scalar", _ApiDoc::openapi())) - } -} diff --git a/thoughts-backend/doc/src/root.rs b/thoughts-backend/doc/src/root.rs deleted file mode 100644 index 583a58a..0000000 --- a/thoughts-backend/doc/src/root.rs +++ /dev/null @@ -1,7 +0,0 @@ -use utoipa::OpenApi; - -use api::routers::root::*; - -#[derive(OpenApi)] -#[openapi(paths(root_get))] -pub(super) struct RootApi; diff --git a/thoughts-backend/doc/src/search.rs b/thoughts-backend/doc/src/search.rs deleted file mode 100644 index dca1dec..0000000 --- a/thoughts-backend/doc/src/search.rs +++ /dev/null @@ -1,21 +0,0 @@ -use api::{models::ApiErrorResponse, routers::search::*}; -use models::schemas::{ - search::SearchResultsSchema, - thought::{ThoughtListSchema, ThoughtSchema}, - user::{UserListSchema, UserSchema}, -}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(search_all), - components(schemas( - SearchResultsSchema, - ApiErrorResponse, - ThoughtSchema, - ThoughtListSchema, - UserSchema, - UserListSchema - )) -)] -pub(super) struct SearchApi; diff --git a/thoughts-backend/doc/src/tag.rs b/thoughts-backend/doc/src/tag.rs deleted file mode 100644 index f5316b9..0000000 --- a/thoughts-backend/doc/src/tag.rs +++ /dev/null @@ -1,12 +0,0 @@ -// in thoughts-backend/doc/src/tag.rs - -use api::{models::ApiErrorResponse, routers::tag::*}; -use models::schemas::thought::{ThoughtListSchema, ThoughtSchema}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(get_thoughts_by_tag, get_popular_tags), - components(schemas(ThoughtSchema, ThoughtListSchema, ApiErrorResponse)) -)] -pub(super) struct TagApi; diff --git a/thoughts-backend/doc/src/thought.rs b/thoughts-backend/doc/src/thought.rs deleted file mode 100644 index 6fa2ad6..0000000 --- a/thoughts-backend/doc/src/thought.rs +++ /dev/null @@ -1,22 +0,0 @@ -use api::{ - models::{ApiErrorResponse, ParamsErrorResponse}, - routers::thought::*, -}; -use models::{ - params::thought::CreateThoughtParams, - schemas::thought::{ThoughtSchema, ThoughtThreadSchema}, -}; -use utoipa::OpenApi; - -#[derive(OpenApi)] -#[openapi( - paths(thoughts_post, thoughts_delete, get_thought_by_id, get_thought_thread), - components(schemas( - CreateThoughtParams, - ThoughtSchema, - ThoughtThreadSchema, - ApiErrorResponse, - ParamsErrorResponse - )) -)] -pub(super) struct ThoughtApi; diff --git a/thoughts-backend/doc/src/user.rs b/thoughts-backend/doc/src/user.rs deleted file mode 100644 index 12b443c..0000000 --- a/thoughts-backend/doc/src/user.rs +++ /dev/null @@ -1,37 +0,0 @@ -use utoipa::OpenApi; - -use api::models::{ApiErrorResponse, ParamsErrorResponse}; -use api::routers::user::*; -use models::params::user::{CreateUserParams, UpdateUserParams}; -use models::schemas::{ - thought::{ThoughtListSchema, ThoughtSchema}, - user::{UserListSchema, UserSchema}, -}; - -#[derive(OpenApi)] -#[openapi( - paths( - users_get, - get_user_by_param, - user_thoughts_get, - user_follow_post, - user_follow_delete, - user_inbox_post, - user_outbox_get, - get_me, - update_me, - get_user_followers, - get_user_following - ), - components(schemas( - CreateUserParams, - UserListSchema, - UpdateUserParams, - UserSchema, - ThoughtSchema, - ThoughtListSchema, - ApiErrorResponse, - ParamsErrorResponse, - )) -)] -pub(super) struct UserApi; diff --git a/thoughts-backend/migration/Cargo.toml b/thoughts-backend/migration/Cargo.toml deleted file mode 100644 index 5546c32..0000000 --- a/thoughts-backend/migration/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "migration" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "migration" -path = "src/lib.rs" - -[dependencies] -models = { path = "../models" } - -async-std = { version = "1.13.1", features = ["attributes", "tokio1"] } -sea-orm-migration = { version = "1.1.12", features = ["sqlx-postgres"] } diff --git a/thoughts-backend/migration/README.md b/thoughts-backend/migration/README.md deleted file mode 100644 index 321da8e..0000000 --- a/thoughts-backend/migration/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Running Migrator CLI - -- Generate a new migration file - - ```sh - cargo run -- generate MIGRATION_NAME - ``` - -- Apply all pending migrations - - ```sh - cargo run - ``` - - ```sh - cargo run -- up - ``` - -- Apply first 10 pending migrations - - ```sh - cargo run -- up -n 10 - ``` - -- Rollback last applied migrations - - ```sh - cargo run -- down - ``` - -- Rollback last 10 applied migrations - - ```sh - cargo run -- down -n 10 - ``` - -- Drop all tables from the database, then reapply all migrations - - ```sh - cargo run -- fresh - ``` - -- Rollback all applied migrations, then reapply all migrations - - ```sh - cargo run -- refresh - ``` - -- Rollback all applied migrations - - ```sh - cargo run -- reset - ``` - -- Check the status of all migrations - - ```sh - cargo run -- status - ``` diff --git a/thoughts-backend/migration/src/lib.rs b/thoughts-backend/migration/src/lib.rs deleted file mode 100644 index 29a68c8..0000000 --- a/thoughts-backend/migration/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub use sea_orm_migration::prelude::*; - -mod m20240101_000001_init; -mod m20250905_000001_init; -mod m20250906_100000_add_profile_fields; -mod m20250906_130237_add_tags; -mod m20250906_134056_add_api_keys; -mod m20250906_145148_add_reply_to_thoughts; -mod m20250906_145755_add_visibility_to_thoughts; -mod m20250906_231359_add_full_text_search; - -pub struct Migrator; - -#[async_trait::async_trait] -impl MigratorTrait for Migrator { - fn migrations() -> Vec> { - vec![ - Box::new(m20240101_000001_init::Migration), - Box::new(m20250905_000001_init::Migration), - Box::new(m20250906_100000_add_profile_fields::Migration), - Box::new(m20250906_130237_add_tags::Migration), - Box::new(m20250906_134056_add_api_keys::Migration), - Box::new(m20250906_145148_add_reply_to_thoughts::Migration), - Box::new(m20250906_145755_add_visibility_to_thoughts::Migration), - Box::new(m20250906_231359_add_full_text_search::Migration), - ] - } -} diff --git a/thoughts-backend/migration/src/m20240101_000001_init.rs b/thoughts-backend/migration/src/m20240101_000001_init.rs deleted file mode 100644 index f23d39c..0000000 --- a/thoughts-backend/migration/src/m20240101_000001_init.rs +++ /dev/null @@ -1,47 +0,0 @@ -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(User::Table) - .if_not_exists() - .col( - ColumnDef::new(User::Id) - .uuid() - .not_null() - .primary_key() - .default(Expr::cust("gen_random_uuid()")), - ) - .col( - ColumnDef::new(User::Username) - .string() - .not_null() - .unique_key(), - ) - .to_owned() - .col(ColumnDef::new(User::PasswordHash).string()) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(User::Table).to_owned()) - .await - } -} - -#[derive(DeriveIden)] -pub(super) enum User { - Table, - Id, - Username, - PasswordHash, -} diff --git a/thoughts-backend/migration/src/m20250905_000001_init.rs b/thoughts-backend/migration/src/m20250905_000001_init.rs deleted file mode 100644 index c31470b..0000000 --- a/thoughts-backend/migration/src/m20250905_000001_init.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::m20240101_000001_init::User; -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // --- Create Thought Table --- - manager - .create_table( - Table::create() - .table(Thought::Table) - .if_not_exists() - .col( - ColumnDef::new(Thought::Id) - .uuid() - .not_null() - .primary_key() - .default(Expr::cust("gen_random_uuid()")), - ) - .col(uuid(Thought::AuthorId).not_null()) - .foreign_key( - ForeignKey::create() - .name("fk_thought_author_id") - .from(Thought::Table, Thought::AuthorId) - .to(User::Table, User::Id) - .on_update(ForeignKeyAction::NoAction) - .on_delete(ForeignKeyAction::Cascade), - ) - .col(string(Thought::Content).not_null()) - .col( - timestamp_with_time_zone(Thought::CreatedAt) - .not_null() - .default(Expr::current_timestamp()), - ) - .to_owned(), - ) - .await?; - - // --- Create Follow Table --- - manager - .create_table( - Table::create() - .table(Follow::Table) - .if_not_exists() - .col(uuid(Follow::FollowerId).not_null()) - .col(uuid(Follow::FollowingId).not_null()) - // Composite Primary Key to ensure a user can only follow another once - .primary_key( - Index::create() - .col(Follow::FollowerId) - .col(Follow::FollowingId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_follow_follower_id") - .from(Follow::Table, Follow::FollowerId) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .foreign_key( - ForeignKey::create() - .name("fk_follow_following_id") - .from(Follow::Table, Follow::FollowingId) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Follow::Table).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(Thought::Table).to_owned()) - .await - } -} - -#[derive(DeriveIden)] -pub enum Thought { - Table, - Id, - AuthorId, - Content, - CreatedAt, -} - -#[derive(DeriveIden)] -pub enum Follow { - Table, - // The user who is initiating the follow - FollowerId, - // The user who is being followed - FollowingId, -} diff --git a/thoughts-backend/migration/src/m20250906_100000_add_profile_fields.rs b/thoughts-backend/migration/src/m20250906_100000_add_profile_fields.rs deleted file mode 100644 index 58749c3..0000000 --- a/thoughts-backend/migration/src/m20250906_100000_add_profile_fields.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::m20240101_000001_init::User; -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .alter_table( - Table::alter() - .table(User::Table) - .add_column(string_null(UserExtension::Email).unique_key()) - .add_column(string_null(UserExtension::DisplayName)) - .add_column(string_null(UserExtension::Bio)) - .add_column(text_null(UserExtension::AvatarUrl)) - .add_column(text_null(UserExtension::HeaderUrl)) - .add_column(text_null(UserExtension::CustomCss)) - .add_column( - timestamp_with_time_zone(UserExtension::CreatedAt) - .not_null() - .default(Expr::current_timestamp()), - ) - .add_column( - timestamp_with_time_zone(UserExtension::UpdatedAt) - .not_null() - .default(Expr::current_timestamp()), - ) - .to_owned(), - ) - .await?; - - manager - .create_table( - Table::create() - .table(TopFriends::Table) - .if_not_exists() - .col(uuid(TopFriends::UserId).not_null()) - .col(uuid(TopFriends::FriendId).not_null()) - .col(small_integer(TopFriends::Position).not_null()) - .primary_key( - Index::create() - .col(TopFriends::UserId) - .col(TopFriends::FriendId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_top_friends_user_id") - .from(TopFriends::Table, TopFriends::UserId) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .foreign_key( - ForeignKey::create() - .name("fk_top_friends_friend_id") - .from(TopFriends::Table, TopFriends::FriendId) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(TopFriends::Table).to_owned()) - .await?; - - manager - .alter_table( - Table::alter() - .table(User::Table) - .drop_column(UserExtension::Email) - .drop_column(UserExtension::DisplayName) - .drop_column(UserExtension::Bio) - .drop_column(UserExtension::AvatarUrl) - .drop_column(UserExtension::HeaderUrl) - .drop_column(UserExtension::CustomCss) - .drop_column(UserExtension::CreatedAt) - .drop_column(UserExtension::UpdatedAt) - .to_owned(), - ) - .await - } -} - -#[derive(DeriveIden)] -enum UserExtension { - Email, - DisplayName, - Bio, - AvatarUrl, - HeaderUrl, - CustomCss, - CreatedAt, - UpdatedAt, -} - -#[derive(DeriveIden)] -enum TopFriends { - Table, - UserId, - FriendId, - Position, -} diff --git a/thoughts-backend/migration/src/m20250906_130237_add_tags.rs b/thoughts-backend/migration/src/m20250906_130237_add_tags.rs deleted file mode 100644 index 99fa2b5..0000000 --- a/thoughts-backend/migration/src/m20250906_130237_add_tags.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::m20250905_000001_init::Thought; -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(Tag::Table) - .if_not_exists() - .col(pk_auto(Tag::Id)) - .col(string(Tag::Name).not_null().unique_key()) - .to_owned(), - ) - .await?; - - manager - .create_table( - Table::create() - .table(ThoughtTag::Table) - .if_not_exists() - .col(uuid(ThoughtTag::ThoughtId).not_null()) - .col(integer(ThoughtTag::TagId).not_null()) - .primary_key( - Index::create() - .col(ThoughtTag::ThoughtId) - .col(ThoughtTag::TagId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_thought_tag_thought_id") - .from(ThoughtTag::Table, ThoughtTag::ThoughtId) - .to(Thought::Table, Thought::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .foreign_key( - ForeignKey::create() - .name("fk_thought_tag_tag_id") - .from(ThoughtTag::Table, ThoughtTag::TagId) - .to(Tag::Table, Tag::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ThoughtTag::Table).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(Tag::Table).to_owned()) - .await - } -} - -#[derive(DeriveIden)] -enum Tag { - Table, - Id, - Name, -} - -#[derive(DeriveIden)] -enum ThoughtTag { - Table, - ThoughtId, - TagId, -} diff --git a/thoughts-backend/migration/src/m20250906_134056_add_api_keys.rs b/thoughts-backend/migration/src/m20250906_134056_add_api_keys.rs deleted file mode 100644 index cd01004..0000000 --- a/thoughts-backend/migration/src/m20250906_134056_add_api_keys.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::m20240101_000001_init::User; -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(ApiKey::Table) - .if_not_exists() - .col( - ColumnDef::new(ApiKey::Id) - .uuid() - .not_null() - .primary_key() - .default(Expr::cust("gen_random_uuid()")), - ) - .col(uuid(ApiKey::UserId).not_null()) - .foreign_key( - ForeignKey::create() - .name("fk_api_key_user_id") - .from(ApiKey::Table, ApiKey::UserId) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .col(text(ApiKey::KeyHash).not_null().unique_key()) - .col(string(ApiKey::Name).not_null()) - .col( - timestamp_with_time_zone(ApiKey::CreatedAt) - .not_null() - .default(Expr::current_timestamp()), - ) - .col(ColumnDef::new(ApiKey::KeyPrefix).string_len(8).not_null()) - .to_owned(), - ) - .await?; - - manager - .create_index( - Index::create() - .name("idx-api_keys-key_prefix") - .table(ApiKey::Table) - .col(ApiKey::KeyPrefix) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ApiKey::Table).to_owned()) - .await - } -} - -#[derive(DeriveIden)] -enum ApiKey { - Table, - Id, - UserId, - KeyHash, - Name, - CreatedAt, - KeyPrefix, -} diff --git a/thoughts-backend/migration/src/m20250906_145148_add_reply_to_thoughts.rs b/thoughts-backend/migration/src/m20250906_145148_add_reply_to_thoughts.rs deleted file mode 100644 index fe92835..0000000 --- a/thoughts-backend/migration/src/m20250906_145148_add_reply_to_thoughts.rs +++ /dev/null @@ -1,46 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::m20250905_000001_init::Thought; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .alter_table( - Table::alter() - .table(Thought::Table) - .add_column(uuid_null(ThoughtExtension::ReplyToId)) - .add_foreign_key( - TableForeignKey::new() - .name("fk_thought_reply_to_id") - .from_tbl(Thought::Table) - .from_col(ThoughtExtension::ReplyToId) - .to_tbl(Thought::Table) - .to_col(Thought::Id) - .on_delete(ForeignKeyAction::SetNull), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .alter_table( - Table::alter() - .table(Thought::Table) - .drop_foreign_key(Alias::new("fk_thought_reply_to_id")) - .drop_column(ThoughtExtension::ReplyToId) - .to_owned(), - ) - .await - } -} - -#[derive(DeriveIden)] -enum ThoughtExtension { - ReplyToId, -} diff --git a/thoughts-backend/migration/src/m20250906_145755_add_visibility_to_thoughts.rs b/thoughts-backend/migration/src/m20250906_145755_add_visibility_to_thoughts.rs deleted file mode 100644 index edefdff..0000000 --- a/thoughts-backend/migration/src/m20250906_145755_add_visibility_to_thoughts.rs +++ /dev/null @@ -1,59 +0,0 @@ -use super::m20250905_000001_init::Thought; -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .get_connection() - .execute_unprepared( - "CREATE TYPE thought_visibility AS ENUM ('public', 'friends_only', 'private')", - ) - .await?; - - // 2. Add the new column to the thoughts table - manager - .alter_table( - Table::alter() - .table(Thought::Table) - .add_column( - ColumnDef::new(ThoughtExtension::Visibility) - .enumeration( - "thought_visibility", - ["public", "friends_only", "private"], - ) - .not_null() - .default("public"), // Default new thoughts to public - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .alter_table( - Table::alter() - .table(Thought::Table) - .drop_column(ThoughtExtension::Visibility) - .to_owned(), - ) - .await?; - - // Drop the ENUM type - manager - .get_connection() - .execute_unprepared("DROP TYPE thought_visibility") - .await?; - - Ok(()) - } -} - -#[derive(DeriveIden)] -enum ThoughtExtension { - Visibility, -} diff --git a/thoughts-backend/migration/src/m20250906_231359_add_full_text_search.rs b/thoughts-backend/migration/src/m20250906_231359_add_full_text_search.rs deleted file mode 100644 index ce21b99..0000000 --- a/thoughts-backend/migration/src/m20250906_231359_add_full_text_search.rs +++ /dev/null @@ -1,48 +0,0 @@ -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // --- Users Table --- - // Add the tsvector column for users - manager.get_connection().execute_unprepared( - "ALTER TABLE \"user\" ADD COLUMN \"search_document\" tsvector \ - GENERATED ALWAYS AS (to_tsvector('english', username || ' ' || coalesce(display_name, ''))) STORED" - ).await?; - // Add the GIN index for users - manager.get_connection().execute_unprepared( - "CREATE INDEX \"user_search_document_idx\" ON \"user\" USING GIN(\"search_document\")" - ).await?; - - // --- Thoughts Table --- - // Add the tsvector column for thoughts - manager - .get_connection() - .execute_unprepared( - "ALTER TABLE \"thought\" ADD COLUMN \"search_document\" tsvector \ - GENERATED ALWAYS AS (to_tsvector('english', content)) STORED", - ) - .await?; - // Add the GIN index for thoughts - manager.get_connection().execute_unprepared( - "CREATE INDEX \"thought_search_document_idx\" ON \"thought\" USING GIN(\"search_document\")" - ).await?; - - Ok(()) - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .get_connection() - .execute_unprepared("ALTER TABLE \"user\" DROP COLUMN \"search_document\"") - .await?; - manager - .get_connection() - .execute_unprepared("ALTER TABLE \"thought\" DROP COLUMN \"search_document\"") - .await?; - Ok(()) - } -} diff --git a/thoughts-backend/migration/src/main.rs b/thoughts-backend/migration/src/main.rs deleted file mode 100644 index c6b6e48..0000000 --- a/thoughts-backend/migration/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -use sea_orm_migration::prelude::*; - -#[async_std::main] -async fn main() { - cli::run_cli(migration::Migrator).await; -} diff --git a/thoughts-backend/models/Cargo.toml b/thoughts-backend/models/Cargo.toml deleted file mode 100644 index 7e2cae5..0000000 --- a/thoughts-backend/models/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "models" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "models" -path = "src/lib.rs" - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -sea-orm = { workspace = true, features = [ - "sqlx-postgres", - "runtime-tokio-rustls", - "macros", -] } -uuid = { version = "1.18.1", features = ["v4", "serde"] } -validator = { workspace = true, features = ["derive"] } -utoipa = { workspace = true } - -common = { path = "../common" } diff --git a/thoughts-backend/models/README.md b/thoughts-backend/models/README.md deleted file mode 100644 index 1d1e818..0000000 --- a/thoughts-backend/models/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# models - -No axum or api dependencies should be introduced into this folder. -Only dependencies for modelling are allowed: - -- serde (JSON serialization/deserialization) -- SeaORM (domain models and database) -- validator (parameter validation) -- utoipa (openapi) - -## SeaORM - -Write migration files first, then generate models. diff --git a/thoughts-backend/models/src/domains/api_key.rs b/thoughts-backend/models/src/domains/api_key.rs deleted file mode 100644 index 845919f..0000000 --- a/thoughts-backend/models/src/domains/api_key.rs +++ /dev/null @@ -1,32 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "api_key")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - pub user_id: Uuid, - pub key_prefix: String, - #[sea_orm(unique)] - pub key_hash: String, - pub name: String, - pub created_at: DateTimeWithTimeZone, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::UserId", - to = "super::user::Column::Id" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/follow.rs b/thoughts-backend/models/src/domains/follow.rs deleted file mode 100644 index 99fb41a..0000000 --- a/thoughts-backend/models/src/domains/follow.rs +++ /dev/null @@ -1,38 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "follow")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub follower_id: Uuid, - #[sea_orm(primary_key, auto_increment = false)] - pub following_id: Uuid, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::FollowerId", - to = "super::user::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Follower, - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::FollowingId", - to = "super::user::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Following, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Follower.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/mod.rs b/thoughts-backend/models/src/domains/mod.rs deleted file mode 100644 index f1cd63e..0000000 --- a/thoughts-backend/models/src/domains/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 - -pub mod prelude; - -pub mod api_key; -pub mod follow; -pub mod tag; -pub mod thought; -pub mod thought_tag; -pub mod top_friends; -pub mod user; diff --git a/thoughts-backend/models/src/domains/prelude.rs b/thoughts-backend/models/src/domains/prelude.rs deleted file mode 100644 index 3028691..0000000 --- a/thoughts-backend/models/src/domains/prelude.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 - -pub use super::api_key::Entity as ApiKey; -pub use super::follow::Entity as Follow; -pub use super::tag::Entity as Tag; -pub use super::thought::Entity as Thought; -pub use super::thought_tag::Entity as ThoughtTag; -pub use super::top_friends::Entity as TopFriends; -pub use super::user::Entity as User; diff --git a/thoughts-backend/models/src/domains/tag.rs b/thoughts-backend/models/src/domains/tag.rs deleted file mode 100644 index 2625ee8..0000000 --- a/thoughts-backend/models/src/domains/tag.rs +++ /dev/null @@ -1,27 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "tag")] -pub struct Model { - #[sea_orm(primary_key)] - pub id: i32, - #[sea_orm(unique)] - pub name: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::thought_tag::Entity")] - ThoughtTag, -} - -impl Related for Entity { - fn to() -> RelationDef { - super::thought_tag::Relation::Thought.def() - } - fn via() -> Option { - Some(super::thought_tag::Relation::Tag.def().rev()) - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/thought.rs b/thoughts-backend/models/src/domains/thought.rs deleted file mode 100644 index 92f1640..0000000 --- a/thoughts-backend/models/src/domains/thought.rs +++ /dev/null @@ -1,62 +0,0 @@ -use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; - -#[derive( - Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize, ToSchema, -)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "thought_visibility")] -pub enum Visibility { - #[sea_orm(string_value = "public")] - Public, - #[sea_orm(string_value = "friends_only")] - FriendsOnly, - #[sea_orm(string_value = "private")] - Private, -} - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "thought")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - pub author_id: Uuid, - pub content: String, - pub reply_to_id: Option, - pub visibility: Visibility, - pub created_at: DateTimeWithTimeZone, - #[sea_orm(column_type = "custom(\"tsvector\")", nullable, ignore)] - pub search_document: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::AuthorId", - to = "super::user::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - User, - - #[sea_orm(has_many = "super::thought_tag::Entity")] - ThoughtTag, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::thought_tag::Relation::Tag.def() - } - fn via() -> Option { - Some(super::thought_tag::Relation::Thought.def().rev()) - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/thought_tag.rs b/thoughts-backend/models/src/domains/thought_tag.rs deleted file mode 100644 index f84f0a1..0000000 --- a/thoughts-backend/models/src/domains/thought_tag.rs +++ /dev/null @@ -1,40 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "thought_tag")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub thought_id: Uuid, - #[sea_orm(primary_key, auto_increment = false)] - pub tag_id: i32, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::thought::Entity", - from = "Column::ThoughtId", - to = "super::thought::Column::Id" - )] - Thought, - #[sea_orm( - belongs_to = "super::tag::Entity", - from = "Column::TagId", - to = "super::tag::Column::Id" - )] - Tag, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Thought.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Tag.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/top_friends.rs b/thoughts-backend/models/src/domains/top_friends.rs deleted file mode 100644 index 4995888..0000000 --- a/thoughts-backend/models/src/domains/top_friends.rs +++ /dev/null @@ -1,35 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "top_friends")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub user_id: Uuid, - #[sea_orm(primary_key, auto_increment = false)] - pub friend_id: Uuid, - pub position: i16, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::UserId", - to = "super::user::Column::Id" - )] - User, - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::FriendId", - to = "super::user::Column::Id" - )] - Friend, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/domains/user.rs b/thoughts-backend/models/src/domains/user.rs deleted file mode 100644 index 973b908..0000000 --- a/thoughts-backend/models/src/domains/user.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "user")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - #[sea_orm(unique)] - pub username: String, - pub password_hash: Option, - #[sea_orm(unique)] - pub email: Option, - pub display_name: Option, - pub bio: Option, - pub avatar_url: Option, - pub header_url: Option, - pub custom_css: Option, - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, - #[sea_orm(column_type = "custom(\"tsvector\")", nullable, ignore)] - pub search_document: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::thought::Entity")] - Thought, - - #[sea_orm(has_many = "super::top_friends::Entity")] - TopFriends, - - #[sea_orm(has_many = "super::api_key::Entity")] - ApiKey, -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/thoughts-backend/models/src/lib.rs b/thoughts-backend/models/src/lib.rs deleted file mode 100644 index f3ddea8..0000000 --- a/thoughts-backend/models/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod domains; -pub mod params; -pub mod queries; -pub mod schemas; diff --git a/thoughts-backend/models/src/params/auth.rs b/thoughts-backend/models/src/params/auth.rs deleted file mode 100644 index 56694b3..0000000 --- a/thoughts-backend/models/src/params/auth.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Deserialize; -use utoipa::ToSchema; -use validator::Validate; - -#[derive(Deserialize, Validate, ToSchema)] -pub struct RegisterParams { - #[validate(length(min = 3))] - pub username: String, - #[validate(email)] - pub email: String, - #[validate(length(min = 6))] - pub password: String, -} - -#[derive(Deserialize, Validate, ToSchema)] -pub struct LoginParams { - #[validate(length(min = 3))] - pub username: String, - #[validate(length(min = 6))] - pub password: String, -} diff --git a/thoughts-backend/models/src/params/mod.rs b/thoughts-backend/models/src/params/mod.rs deleted file mode 100644 index f41bb46..0000000 --- a/thoughts-backend/models/src/params/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod auth; -pub mod thought; -pub mod user; diff --git a/thoughts-backend/models/src/params/thought.rs b/thoughts-backend/models/src/params/thought.rs deleted file mode 100644 index ca500ba..0000000 --- a/thoughts-backend/models/src/params/thought.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::Deserialize; -use utoipa::ToSchema; -use uuid::Uuid; -use validator::Validate; - -use crate::domains::thought::Visibility; - -#[derive(Deserialize, Validate, ToSchema)] -pub struct CreateThoughtParams { - #[validate(length( - min = 1, - max = 128, - message = "Content must be between 1 and 128 characters" - ))] - pub content: String, - pub visibility: Option, - #[serde(rename = "replyToId")] - pub reply_to_id: Option, -} diff --git a/thoughts-backend/models/src/params/user.rs b/thoughts-backend/models/src/params/user.rs deleted file mode 100644 index 0eebbc3..0000000 --- a/thoughts-backend/models/src/params/user.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::Deserialize; -use utoipa::ToSchema; -use validator::Validate; - -#[derive(Deserialize, Validate, ToSchema)] -pub struct CreateUserParams { - #[validate(length(min = 2))] - pub username: String, - #[validate(length(min = 6))] - pub password: String, -} - -#[derive(Deserialize, Validate, ToSchema, Default)] -pub struct UpdateUserParams { - #[validate(length(max = 50))] - #[schema(example = "Frutiger Aero Fan")] - #[serde(rename = "displayName")] - pub display_name: Option, - - #[validate(length(max = 4000))] - #[schema(example = "Est. 2004")] - pub bio: Option, - - #[validate(url)] - #[serde(rename = "avatarUrl")] - pub avatar_url: Option, - - #[validate(url)] - #[serde(rename = "headerUrl")] - pub header_url: Option, - #[serde(rename = "customCss")] - pub custom_css: Option, - - #[validate(length(max = 8))] - #[schema(example = json!(["username1", "username2"]))] - #[serde(rename = "topFriends")] - pub top_friends: Option>, -} diff --git a/thoughts-backend/models/src/queries/mod.rs b/thoughts-backend/models/src/queries/mod.rs deleted file mode 100644 index b038f7f..0000000 --- a/thoughts-backend/models/src/queries/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod pagination; -pub mod user; diff --git a/thoughts-backend/models/src/queries/pagination.rs b/thoughts-backend/models/src/queries/pagination.rs deleted file mode 100644 index d0a79eb..0000000 --- a/thoughts-backend/models/src/queries/pagination.rs +++ /dev/null @@ -1,27 +0,0 @@ -use serde::Deserialize; -use utoipa::IntoParams; - -const DEFAULT_PAGE: u64 = 1; -const DEFAULT_PAGE_SIZE: u64 = 20; - -#[derive(Deserialize, IntoParams)] -pub struct PaginationQuery { - #[param(nullable = true, example = 1)] - page: Option, - #[param(nullable = true, example = 20)] - page_size: Option, -} - -impl PaginationQuery { - pub fn page(&self) -> u64 { - self.page.unwrap_or(DEFAULT_PAGE).max(1) - } - - pub fn page_size(&self) -> u64 { - self.page_size.unwrap_or(DEFAULT_PAGE_SIZE).max(1) - } - - pub fn offset(&self) -> u64 { - (self.page() - 1) * self.page_size() - } -} diff --git a/thoughts-backend/models/src/queries/user.rs b/thoughts-backend/models/src/queries/user.rs deleted file mode 100644 index 1b42a04..0000000 --- a/thoughts-backend/models/src/queries/user.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::Deserialize; -use utoipa::IntoParams; - -#[derive(Deserialize, Default, IntoParams)] -#[into_params(style = Form, parameter_in = Query)] -pub struct UserQuery { - #[param(nullable = true)] - pub username: Option, -} diff --git a/thoughts-backend/models/src/schemas/api_key.rs b/thoughts-backend/models/src/schemas/api_key.rs deleted file mode 100644 index 54385ae..0000000 --- a/thoughts-backend/models/src/schemas/api_key.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::domains::api_key; -use common::DateTimeWithTimeZoneWrapper; -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; -use uuid::Uuid; - -#[derive(Serialize, ToSchema)] -pub struct ApiKeySchema { - pub id: Uuid, - pub name: String, - #[serde(rename = "keyPrefix")] - pub key_prefix: String, - #[serde(rename = "createdAt")] - pub created_at: DateTimeWithTimeZoneWrapper, -} - -#[derive(Serialize, ToSchema)] -pub struct ApiKeyResponse { - #[serde(flatten)] - pub key: ApiKeySchema, - #[serde(skip_serializing_if = "Option::is_none", rename = "plaintextKey")] - pub plaintext_key: Option, -} - -impl ApiKeyResponse { - pub fn from_parts(model: api_key::Model, plaintext_key: Option) -> Self { - Self { - key: ApiKeySchema { - id: model.id, - name: model.name, - key_prefix: model.key_prefix, - created_at: model.created_at.into(), - }, - plaintext_key, - } - } -} - -#[derive(Serialize, ToSchema)] -pub struct ApiKeyListSchema { - #[serde(rename = "apiKeys")] - pub api_keys: Vec, -} - -impl From> for ApiKeyListSchema { - fn from(keys: Vec) -> Self { - Self { - api_keys: keys - .into_iter() - .map(|k| ApiKeySchema { - id: k.id, - name: k.name, - key_prefix: k.key_prefix, - created_at: k.created_at.into(), - }) - .collect(), - } - } -} - -#[derive(Deserialize, ToSchema)] -pub struct ApiKeyRequest { - pub name: String, -} diff --git a/thoughts-backend/models/src/schemas/mod.rs b/thoughts-backend/models/src/schemas/mod.rs deleted file mode 100644 index 2703119..0000000 --- a/thoughts-backend/models/src/schemas/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod api_key; -pub mod pagination; -pub mod search; -pub mod thought; -pub mod user; diff --git a/thoughts-backend/models/src/schemas/pagination.rs b/thoughts-backend/models/src/schemas/pagination.rs deleted file mode 100644 index 5bcf608..0000000 --- a/thoughts-backend/models/src/schemas/pagination.rs +++ /dev/null @@ -1,12 +0,0 @@ -use serde::Serialize; -use utoipa::ToSchema; - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PaginatedResponse { - pub items: Vec, - pub page: u64, - pub page_size: u64, - pub total_pages: u64, - pub total_items: u64, -} diff --git a/thoughts-backend/models/src/schemas/search.rs b/thoughts-backend/models/src/schemas/search.rs deleted file mode 100644 index 7bda3ff..0000000 --- a/thoughts-backend/models/src/schemas/search.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::{thought::ThoughtListSchema, user::UserListSchema}; -use serde::Serialize; -use utoipa::ToSchema; - -#[derive(Serialize, ToSchema)] -pub struct SearchResultsSchema { - pub users: UserListSchema, - pub thoughts: ThoughtListSchema, -} diff --git a/thoughts-backend/models/src/schemas/thought.rs b/thoughts-backend/models/src/schemas/thought.rs deleted file mode 100644 index f54ce03..0000000 --- a/thoughts-backend/models/src/schemas/thought.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::domains::{ - thought::{self, Visibility}, - user, -}; -use common::DateTimeWithTimeZoneWrapper; -use sea_orm::FromQueryResult; -use serde::Serialize; -use utoipa::ToSchema; -use uuid::Uuid; - -#[derive(Serialize, ToSchema, FromQueryResult, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ThoughtSchema { - pub id: Uuid, - #[schema(example = "frutiger")] - pub author_username: String, - pub author_display_name: Option, - #[schema(example = "This is my first thought! #welcome")] - pub content: String, - pub visibility: Visibility, - pub reply_to_id: Option, - pub created_at: DateTimeWithTimeZoneWrapper, -} - -impl ThoughtSchema { - pub fn from_models(thought: &thought::Model, author: &user::Model) -> Self { - Self { - id: thought.id, - author_username: author.username.clone(), - author_display_name: author.display_name.clone(), - content: thought.content.clone(), - visibility: thought.visibility.clone(), - reply_to_id: thought.reply_to_id, - created_at: thought.created_at.into(), - } - } -} - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ThoughtListSchema { - pub thoughts: Vec, -} - -impl From> for ThoughtListSchema { - fn from(thoughts: Vec) -> Self { - Self { thoughts } - } -} - -#[derive(Debug, FromQueryResult)] -pub struct ThoughtWithAuthor { - pub id: Uuid, - pub content: String, - pub created_at: sea_orm::prelude::DateTimeWithTimeZone, - pub visibility: Visibility, - pub author_id: Uuid, - pub author_username: String, - pub author_display_name: Option, - pub reply_to_id: Option, -} - -impl From for ThoughtSchema { - fn from(model: ThoughtWithAuthor) -> Self { - Self { - id: model.id, - author_username: model.author_username, - author_display_name: model.author_display_name, - content: model.content, - created_at: model.created_at.into(), - reply_to_id: model.reply_to_id, - visibility: model.visibility, - } - } -} - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ThoughtThreadSchema { - pub id: Uuid, - pub author_username: String, - pub author_display_name: Option, - pub content: String, - pub visibility: Visibility, - pub reply_to_id: Option, - pub created_at: DateTimeWithTimeZoneWrapper, - pub replies: Vec, -} diff --git a/thoughts-backend/models/src/schemas/user.rs b/thoughts-backend/models/src/schemas/user.rs deleted file mode 100644 index 6560b72..0000000 --- a/thoughts-backend/models/src/schemas/user.rs +++ /dev/null @@ -1,80 +0,0 @@ -use common::DateTimeWithTimeZoneWrapper; -use serde::Serialize; -use utoipa::ToSchema; -use uuid::Uuid; - -use crate::domains::user; - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UserSchema { - pub id: Uuid, - pub username: String, - pub display_name: Option, - pub bio: Option, - pub avatar_url: Option, - pub header_url: Option, - pub custom_css: Option, - pub top_friends: Vec, - pub joined_at: DateTimeWithTimeZoneWrapper, -} - -impl From<(user::Model, Vec)> for UserSchema { - fn from((user, top_friends): (user::Model, Vec)) -> Self { - Self { - id: user.id, - username: user.username, - display_name: user.display_name, - bio: user.bio, - avatar_url: user.avatar_url, - header_url: user.header_url, - custom_css: user.custom_css, - top_friends: top_friends.into_iter().map(|u| u.username).collect(), - joined_at: user.created_at.into(), - } - } -} - -impl From for UserSchema { - fn from(user: user::Model) -> Self { - Self { - id: user.id, - username: user.username, - display_name: user.display_name, - bio: user.bio, - avatar_url: user.avatar_url, - header_url: user.header_url, - custom_css: user.custom_css, - top_friends: vec![], - joined_at: user.created_at.into(), - } - } -} - -#[derive(Serialize, ToSchema)] -pub struct UserListSchema { - pub users: Vec, -} - -impl From> for UserListSchema { - fn from(users: Vec) -> Self { - Self { - users: users.into_iter().map(UserSchema::from).collect(), - } - } -} - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct MeSchema { - pub id: Uuid, - pub username: String, - pub display_name: Option, - pub bio: Option, - pub avatar_url: Option, - pub header_url: Option, - pub custom_css: Option, - pub top_friends: Vec, - pub joined_at: DateTimeWithTimeZoneWrapper, - pub following: Vec, -} diff --git a/thoughts-backend/src/main.rs b/thoughts-backend/src/main.rs deleted file mode 100644 index 86ec7b7..0000000 --- a/thoughts-backend/src/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[cfg(not(feature = "shuttle"))] -mod tokio; - -#[cfg(not(feature = "shuttle"))] -fn main() { - use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "api=debug,clean_axum=debug,tower_http=debug".into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); - - tracing::info!("Starting with tokio"); - tokio::run(); -} - -#[cfg(feature = "shuttle")] -mod shuttle; - -#[cfg(feature = "shuttle")] -#[shuttle_runtime::main] -async fn main(#[shuttle_shared_db::Postgres] db_url: String) -> shuttle_axum::ShuttleAxum { - shuttle::run(&db_url).await -} diff --git a/thoughts-backend/src/shuttle.rs b/thoughts-backend/src/shuttle.rs deleted file mode 100644 index 9d57b2b..0000000 --- a/thoughts-backend/src/shuttle.rs +++ /dev/null @@ -1,13 +0,0 @@ -use api::{setup_db, setup_router}; -use doc::ApiDocExt; -use utils::migrate; - -pub async fn run(db_url: &str) -> shuttle_axum::ShuttleAxum { - tracing::info!("Starting with shuttle"); - - let conn = setup_db(&db_url, false).await; - migrate(&conn).await.expect("Migration failed!"); - - let router = setup_router(conn).attach_doc(); - Ok(router.into()) -} diff --git a/thoughts-backend/src/tokio.rs b/thoughts-backend/src/tokio.rs deleted file mode 100644 index 359f8e5..0000000 --- a/thoughts-backend/src/tokio.rs +++ /dev/null @@ -1,55 +0,0 @@ -use api::{setup_config, setup_db, setup_router}; -use utils::migrate; - -async fn worker(child_num: u32, db_url: &str, prefork: bool, listener: std::net::TcpListener) { - let conn = setup_db(db_url, prefork).await; - - if child_num == 0 { - migrate(&conn).await.expect("Migration failed!"); - } - - let config = setup_config(); - - let router = setup_router(conn, &config); - - let listener = tokio::net::TcpListener::from_std(listener).expect("bind to port"); - axum::serve(listener, router).await.expect("start server"); -} - -#[cfg(feature = "prefork")] -fn run_prefork(db_url: &str, listener: std::net::TcpListener) { - let db_url: &'static str = Box::leak(db_url.to_owned().into_boxed_str()); - - let num_of_cores = std::thread::available_parallelism().unwrap().get() as u32; - let is_parent = prefork::Prefork::from_resource(listener) - .with_num_processes(num_of_cores) - .with_tokio(move |child_num, listener| worker(child_num, db_url, true, listener)) - .fork() - .expect("prefork failed"); - - if is_parent { - tracing::info!("All workers stopped"); - } -} - -fn run_non_prefork(db_url: &str, listener: std::net::TcpListener) { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(worker(0, db_url, false, listener)); -} - -pub fn run() { - tracing::info!("Starting server..."); - let config = setup_config(); - - let listener = std::net::TcpListener::bind(config.get_server_url()).expect("bind to port"); - listener.set_nonblocking(true).expect("non blocking failed"); - tracing::info!("listening on http://{}", listener.local_addr().unwrap()); - - #[cfg(feature = "prefork")] - if config.prefork { - run_prefork(&config.db_url, listener); - return; - } - - run_non_prefork(&config.db_url, listener); -} diff --git a/thoughts-backend/tests/api/api_key.rs b/thoughts-backend/tests/api/api_key.rs deleted file mode 100644 index 339e25e..0000000 --- a/thoughts-backend/tests/api/api_key.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::api::main::{create_user_with_password, login_user, setup}; -use axum::http::{header, HeaderName, StatusCode}; -use http_body_util::BodyExt; -use serde_json::{json, Value}; -use utils::testing::{make_jwt_request, make_request_with_headers}; - -#[tokio::test] -async fn test_api_key_flow() { - let app = setup().await; - let _ = create_user_with_password( - &app.db, - "apikey_user", - "password123", - "apikey_user@example.com", - ) - .await; - let jwt = login_user(app.router.clone(), "apikey_user", "password123").await; - - // 1. Create a new API key using JWT auth - let create_body = json!({ "name": "My Test Key" }).to_string(); - let response = make_jwt_request( - app.router.clone(), - "/users/me/api-keys", - "POST", - Some(create_body), - &jwt, - ) - .await; - assert_eq!(response.status(), StatusCode::CREATED); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - let plaintext_key = v["plaintextKey"] - .as_str() - .expect("Plaintext key not found") - .to_string(); - let key_id = v["id"].as_str().expect("Key ID not found").to_string(); - assert!(plaintext_key.starts_with("th_")); - - // 2. Use the new API key to post a thought - - let thought_body = json!({ "content": "Posting with an API key!" }).to_string(); - let key = plaintext_key.clone(); - let api_key_header = format!("ApiKey {}", key); - let content_type = "application/json"; - let headers: Vec<(HeaderName, &str)> = vec![ - (header::AUTHORIZATION, &api_key_header), - (header::CONTENT_TYPE, content_type), - ]; - - let response = make_request_with_headers( - app.router.clone(), - "/thoughts", - "POST", - Some(thought_body), - headers, - ) - .await; - - assert_eq!(response.status(), StatusCode::CREATED); - - // 3. Delete the API key using JWT auth - let response = make_jwt_request( - app.router.clone(), - &format!("/users/me/api-keys/{}", key_id), - "DELETE", - None, - &jwt, - ) - .await; - assert_eq!(response.status(), StatusCode::NO_CONTENT); - - // 4. Try to use the deleted key again, expecting failure - let body = json!({ "content": "This should fail" }).to_string(); - let headers: Vec<(HeaderName, &str)> = vec![ - (header::AUTHORIZATION, &api_key_header), - (header::CONTENT_TYPE, content_type), - ]; - - let response = - make_request_with_headers(app.router.clone(), "/thoughts", "POST", Some(body), headers) - .await; - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); -} diff --git a/thoughts-backend/tests/api/auth.rs b/thoughts-backend/tests/api/auth.rs deleted file mode 100644 index aeec01b..0000000 --- a/thoughts-backend/tests/api/auth.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::api::main::setup; -use axum::http::StatusCode; -use http_body_util::BodyExt; -use serde_json::{json, Value}; -use utils::testing::{make_jwt_request, make_post_request}; - -#[tokio::test] -async fn test_auth_flow() { - std::env::set_var("AUTH_SECRET", "test-secret"); - let app = setup().await; - - let register_body = json!({ - "username": "testuser", - "email": "testuser@example.com", - "password": "password123" - }) - .to_string(); - let response = - make_post_request(app.router.clone(), "/auth/register", register_body, None).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["username"], "testuser"); - - let response = make_post_request( - app.router.clone(), - "/auth/register", - json!({ - "username": "testuser", - "email": "testuser@example.com", - "password": "password456" - }) - .to_string(), - None, - ) - .await; - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let login_body = json!({ - "username": "testuser", - "password": "password123" - }) - .to_string(); - let response = make_post_request(app.router.clone(), "/auth/login", login_body, None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - let token = v["token"].as_str().expect("token not found").to_string(); - assert!(!token.is_empty()); - - let bad_login_body = json!({ - "username": "testuser", - "email": "testuser@example.com", - "password": "wrongpassword" - }) - .to_string(); - let response = make_post_request(app.router.clone(), "/auth/login", bad_login_body, None).await; - assert_eq!(response.status(), StatusCode::NOT_FOUND); - - let response = make_jwt_request(app.router.clone(), "/feed", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); -} diff --git a/thoughts-backend/tests/api/feed.rs b/thoughts-backend/tests/api/feed.rs deleted file mode 100644 index d9a3e6e..0000000 --- a/thoughts-backend/tests/api/feed.rs +++ /dev/null @@ -1,273 +0,0 @@ -use std::time::Duration; - -use super::main::{create_user_with_password, setup}; -use axum::http::StatusCode; -use http_body_util::BodyExt; -use serde_json::{json, Value}; -use tokio::time::sleep; -use utils::testing::make_jwt_request; - -#[tokio::test] -async fn test_feed_and_user_thoughts() { - let app = setup().await; - create_user_with_password(&app.db, "user1", "password1", "user1@example.com").await; - create_user_with_password(&app.db, "user2", "password2", "user2@example.com").await; - create_user_with_password(&app.db, "user3", "password3", "user3@example.com").await; - - // As user1, post a thought - let token = super::main::login_user(app.router.clone(), "user1", "password1").await; - let body = json!({ "content": "A thought from user1" }).to_string(); - make_jwt_request(app.router.clone(), "/thoughts", "POST", Some(body), &token).await; - - // As a different "user", create thoughts for user2 and user3 - let token2 = super::main::login_user(app.router.clone(), "user2", "password2").await; - let body2 = json!({ "content": "user2 was here" }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body2), - &token2, - ) - .await; - - let token3 = super::main::login_user(app.router.clone(), "user3", "password3").await; - let body3 = json!({ "content": "user3 checking in" }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body3), - &token3, - ) - .await; - - // 1. Get thoughts for user2 - should only see their thought plus their own - let response = make_jwt_request( - app.router.clone(), - "/users/user2/thoughts", - "GET", - None, - &token2, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["thoughts"].as_array().unwrap().len(), 1); - assert_eq!(v["thoughts"][0]["content"], "user2 was here"); - - // 2. user1's feed has only their own thought (not following anyone) - let response = make_jwt_request(app.router.clone(), "/feed", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["items"].as_array().unwrap().len(), 1); - assert_eq!(v["items"][0]["authorUsername"], "user1"); - assert_eq!(v["items"][0]["content"], "A thought from user1"); - - // 3. user1 follows user2 - make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "POST", - None, - &token, - ) - .await; - - // 4. user1's feed now has user2's thought - let response = make_jwt_request(app.router.clone(), "/feed", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["items"].as_array().unwrap().len(), 2); - assert_eq!(v["items"][0]["authorUsername"], "user2"); - assert_eq!(v["items"][0]["content"], "user2 was here"); - assert_eq!(v["items"][1]["authorUsername"], "user1"); - assert_eq!(v["items"][1]["content"], "A thought from user1"); -} - -#[tokio::test] -async fn test_feed_strict_chronological_order() { - let app = setup().await; - create_user_with_password(&app.db, "user1", "password123", "u1@e.com").await; - create_user_with_password(&app.db, "user2", "password123", "u2@e.com").await; - create_user_with_password(&app.db, "user3", "password123", "u3@e.com").await; - - let token1 = super::main::login_user(app.router.clone(), "user1", "password123").await; - let token2 = super::main::login_user(app.router.clone(), "user2", "password123").await; - let token3 = super::main::login_user(app.router.clone(), "user3", "password123").await; - - make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "POST", - None, - &token1, - ) - .await; - make_jwt_request( - app.router.clone(), - "/users/user3/follow", - "POST", - None, - &token1, - ) - .await; - - let body_t1 = json!({ "content": "Thought 1 from user2" }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body_t1), - &token2, - ) - .await; - sleep(Duration::from_millis(10)).await; - - let body_t2 = json!({ "content": "Thought 2 from user3" }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body_t2), - &token3, - ) - .await; - sleep(Duration::from_millis(10)).await; - - let body_t3 = json!({ "content": "Thought 3 from user2" }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body_t3), - &token2, - ) - .await; - - let response = make_jwt_request(app.router.clone(), "/feed", "GET", None, &token1).await; - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: serde_json::Value = serde_json::from_slice(&body).unwrap(); - let thoughts = v["items"].as_array().unwrap(); - - assert_eq!( - thoughts.len(), - 3, - "Feed should contain 3 thoughts from followed users and self" - ); - assert_eq!(thoughts[0]["content"], "Thought 3 from user2"); - assert_eq!(thoughts[1]["content"], "Thought 2 from user3"); - assert_eq!(thoughts[2]["content"], "Thought 1 from user2"); -} - -#[tokio::test] -async fn test_feed_pagination() { - let app = setup().await; - - // 1. Setup users - create_user_with_password(&app.db, "user1", "password123", "u1@e.com").await; - create_user_with_password(&app.db, "user2", "password123", "u2@e.com").await; - create_user_with_password(&app.db, "user3", "password123", "u3@e.com").await; - - let token1 = super::main::login_user(app.router.clone(), "user1", "password123").await; - let token2 = super::main::login_user(app.router.clone(), "user2", "password123").await; - let token3 = super::main::login_user(app.router.clone(), "user3", "password123").await; - - // 2. user1 follows user2 and user3 - make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "POST", - None, - &token1, - ) - .await; - make_jwt_request( - app.router.clone(), - "/users/user3/follow", - "POST", - None, - &token1, - ) - .await; - - // 3. Create 25 thoughts from the followed users to test pagination - // user1's feed also includes their own thoughts. - let mut last_thought_content = String::new(); - for i in 0..25 { - let content = format!("Thought number {}", i); - // Alternate who posts to mix up the feed - let token_to_use = match i % 3 { - 0 => &token2, - 1 => &token3, - _ => &token1, - }; - - let body = json!({ "content": &content }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(body), - token_to_use, - ) - .await; - - if i == 24 { - last_thought_content = content; - } - // Small delay to ensure created_at timestamps are distinct - sleep(Duration::from_millis(5)).await; - } - - // 4. Request the first page (default size 20) - let response_p1 = make_jwt_request( - app.router.clone(), - "/feed?page=1&page_size=20", - "GET", - None, - &token1, - ) - .await; - assert_eq!(response_p1.status(), StatusCode::OK); - let body_p1 = response_p1.into_body().collect().await.unwrap().to_bytes(); - let v_p1: Value = serde_json::from_slice(&body_p1).unwrap(); - - assert_eq!( - v_p1["items"].as_array().unwrap().len(), - 20, - "First page should have 20 items" - ); - assert_eq!(v_p1["page"], 1); - assert_eq!(v_p1["pageSize"], 20); - assert_eq!(v_p1["totalPages"], 2); - assert_eq!(v_p1["totalItems"], 25); - - // Verify the newest thought is first on the first page - assert_eq!(v_p1["items"][0]["content"], last_thought_content); - - // 5. Request the second page - let response_p2 = make_jwt_request( - app.router.clone(), - "/feed?page=2&page_size=20", - "GET", - None, - &token1, - ) - .await; - assert_eq!(response_p2.status(), StatusCode::OK); - let body_p2 = response_p2.into_body().collect().await.unwrap().to_bytes(); - let v_p2: Value = serde_json::from_slice(&body_p2).unwrap(); - - assert_eq!( - v_p2["items"].as_array().unwrap().len(), - 5, - "Second page should have the remaining 5 items" - ); - assert_eq!(v_p2["page"], 2); -} diff --git a/thoughts-backend/tests/api/follow.rs b/thoughts-backend/tests/api/follow.rs deleted file mode 100644 index ce60bb7..0000000 --- a/thoughts-backend/tests/api/follow.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::api::main::login_user; - -use super::main::{create_user_with_password, setup}; -use axum::http::StatusCode; -use http_body_util::BodyExt; -use serde_json::Value; -use utils::testing::{make_get_request, make_jwt_request}; - -#[tokio::test] -async fn test_follow_endpoints() { - std::env::set_var("AUTH_SECRET", "test-secret"); - let app = setup().await; - - create_user_with_password(&app.db, "user1", "password1", "user1@example.com").await; - create_user_with_password(&app.db, "user2", "password2", "user2@example.com").await; - - let token = super::main::login_user(app.router.clone(), "user1", "password1").await; - - // 1. user1 follows user2 - let response = make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "POST", - None, - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::NO_CONTENT); - - // 2. user1 tries to follow user2 again (should fail) - let response = make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "POST", - None, - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - // 3. user1 tries to follow a non-existent user - let response = make_jwt_request( - app.router.clone(), - "/users/nobody/follow", - "POST", - None, - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::NOT_FOUND); - - // 4. user1 unfollows user2 - let response = make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "DELETE", - None, - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::NO_CONTENT); - - // 5. user1 tries to unfollow user2 again (should fail) - let response = make_jwt_request( - app.router.clone(), - "/users/user2/follow", - "DELETE", - None, - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_follow_lists() { - let app = setup().await; - - let user_a = create_user_with_password(&app.db, "userA", "password123", "a@a.com").await; - let user_b = create_user_with_password(&app.db, "userB", "password123", "b@b.com").await; - let user_c = create_user_with_password(&app.db, "userC", "password123", "c@c.com").await; - - // A follows B, C follows A - app::persistence::follow::follow_user(&app.db, user_a.id, user_b.id) - .await - .unwrap(); - app::persistence::follow::follow_user(&app.db, user_c.id, user_a.id) - .await - .unwrap(); - - // 1. Check user A's lists - let response_following = - make_get_request(app.router.clone(), "/users/userA/following", None).await; - let body_following = response_following - .into_body() - .collect() - .await - .unwrap() - .to_bytes(); - let v: Value = serde_json::from_slice(&body_following).unwrap(); - assert_eq!(v["users"].as_array().unwrap().len(), 1); - assert_eq!(v["users"][0]["username"], "userB"); - - let response_followers = - make_get_request(app.router.clone(), "/users/userA/followers", None).await; - let body_followers = response_followers - .into_body() - .collect() - .await - .unwrap() - .to_bytes(); - let v: Value = serde_json::from_slice(&body_followers).unwrap(); - assert_eq!(v["users"].as_array().unwrap().len(), 1); - assert_eq!(v["users"][0]["username"], "userC"); - - // 2. Check user A's /me endpoint - let jwt_a = login_user(app.router.clone(), "userA", "password123").await; - let response_me = make_jwt_request(app.router.clone(), "/users/me", "GET", None, &jwt_a).await; - let body_me = response_me.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body_me).unwrap(); - assert_eq!(v["username"], "userA"); - assert_eq!(v["following"].as_array().unwrap().len(), 1); - assert_eq!(v["following"][0]["username"], "userB"); -} - -#[tokio::test] -async fn test_get_friends_list() { - let app = setup().await; - - let user_a = create_user_with_password(&app.db, "userA", "password123", "a@a.com").await; - let user_b = create_user_with_password(&app.db, "userB", "password123", "b@b.com").await; - let user_c = create_user_with_password(&app.db, "userC", "password123", "c@c.com").await; - - // --- Create relationships --- - // A and B are friends (reciprocal follow) - app::persistence::follow::follow_user(&app.db, user_a.id, user_b.id) - .await - .unwrap(); - app::persistence::follow::follow_user(&app.db, user_b.id, user_a.id) - .await - .unwrap(); - - // A follows C, but C does not follow A back - app::persistence::follow::follow_user(&app.db, user_a.id, user_c.id) - .await - .unwrap(); - - // --- Test as user_a --- - let jwt_a = login_user(app.router.clone(), "userA", "password123").await; - let response = make_jwt_request(app.router.clone(), "/friends", "GET", None, &jwt_a).await; - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - let friends_list = v["users"].as_array().unwrap(); - - assert_eq!(friends_list.len(), 1, "User A should only have one friend"); - assert_eq!( - friends_list[0]["username"], "userB", - "User B should be in User A's friend list" - ); -} diff --git a/thoughts-backend/tests/api/main.rs b/thoughts-backend/tests/api/main.rs deleted file mode 100644 index 79bf5d9..0000000 --- a/thoughts-backend/tests/api/main.rs +++ /dev/null @@ -1,59 +0,0 @@ -use api::setup_router; -use axum::Router; -use http_body_util::BodyExt; -use models::{domains::user, params::auth::RegisterParams}; -use sea_orm::DatabaseConnection; -use serde_json::{json, Value}; -use utils::testing::{make_post_request, setup_test_db}; - -pub struct TestApp { - pub router: Router, - pub db: DatabaseConnection, -} - -pub async fn setup() -> TestApp { - std::env::set_var( - "MANAGEMENT_DATABASE_URL", - "postgres://postgres:postgres@localhost:5434/postgres", - ); - std::env::set_var( - "DATABASE_URL", - "postgres://postgres:postgres@localhost:5434/postgres", - ); - std::env::set_var("AUTH_SECRET", "test_secret"); - std::env::set_var("BASE_URL", "http://localhost:3000"); - std::env::set_var("HOST", "localhost"); - std::env::set_var("PORT", "3000"); - std::env::set_var("LOG_LEVEL", "debug"); - - let db = setup_test_db().await.expect("Failed to set up test db"); - - let db = db.clone(); - - let router = setup_router(db.clone(), &app::config::Config::from_env()); - TestApp { router, db } -} - -pub async fn create_user_with_password( - db: &DatabaseConnection, - username: &str, - password: &str, - email: &str, -) -> user::Model { - let params = RegisterParams { - username: username.to_string(), - password: password.to_string(), - email: email.to_string(), - }; - app::persistence::auth::register_user(db, params) - .await - .expect("Failed to create test user with password") -} - -pub async fn login_user(router: Router, username: &str, password: &str) -> String { - let login_body = json!({ "username": username, "password": password }).to_string(); - let response = make_post_request(router, "/auth/login", login_body, None).await; - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - v["token"].as_str().unwrap().to_string() -} diff --git a/thoughts-backend/tests/api/mod.rs b/thoughts-backend/tests/api/mod.rs deleted file mode 100644 index 31c3edd..0000000 --- a/thoughts-backend/tests/api/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod api_key; -mod auth; -mod feed; -mod follow; -mod main; -mod search; -mod tag; -mod thought; -mod user; diff --git a/thoughts-backend/tests/api/search.rs b/thoughts-backend/tests/api/search.rs deleted file mode 100644 index c0ede48..0000000 --- a/thoughts-backend/tests/api/search.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::api::main::{create_user_with_password, login_user, setup}; -use axum::http::StatusCode; -use http_body_util::BodyExt; -use serde_json::{json, Value}; -use utils::testing::{make_get_request, make_jwt_request}; - -#[tokio::test] -async fn test_search_all() { - let app = setup().await; - - // 1. Setup users and data - let user1 = - create_user_with_password(&app.db, "search_user1", "password123", "s1@test.com").await; - let user2 = - create_user_with_password(&app.db, "search_user2", "password123", "s2@test.com").await; - let _user3 = - create_user_with_password(&app.db, "stranger_user", "password123", "s3@test.com").await; - - // Make user1 and user2 friends - app::persistence::follow::follow_user(&app.db, user1.id, user2.id) - .await - .unwrap(); - app::persistence::follow::follow_user(&app.db, user2.id, user1.id) - .await - .unwrap(); - - let token1 = login_user(app.router.clone(), "search_user1", "password123").await; - let token2 = login_user(app.router.clone(), "search_user2", "password123").await; - let token3 = login_user(app.router.clone(), "stranger_user", "password123").await; - - // User1 posts thoughts with different visibilities - let thought_public = - json!({ "content": "A very public thought about Rust.", "visibility": "Public" }) - .to_string(); - let thought_friends = - json!({ "content": "A friendly thought, just for pals.", "visibility": "FriendsOnly" }) - .to_string(); - let thought_private = - json!({ "content": "A private thought, for my eyes only.", "visibility": "Private" }) - .to_string(); - - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(thought_public), - &token1, - ) - .await; - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(thought_friends), - &token1, - ) - .await; - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(thought_private), - &token1, - ) - .await; - - // 2. Run search tests - - // -- User Search -- - let response = make_get_request(app.router.clone(), "/search?q=search_user1", None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["users"]["users"].as_array().unwrap().len(), 1); - assert_eq!(v["users"]["users"][0]["username"], "search_user1"); - - // -- Thought Search (Public) -- - let response = make_get_request(app.router.clone(), "/search?q=public", None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 1, - "Guest should find public thought" - ); - assert!(v["thoughts"]["thoughts"][0]["content"] - .as_str() - .unwrap() - .contains("public")); - - // -- Thought Search (FriendsOnly) -- - let response = make_jwt_request( - app.router.clone(), - "/search?q=friendly", - "GET", - None, - &token1, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 1, - "Author should find friends thought" - ); - - let response = make_jwt_request( - app.router.clone(), - "/search?q=friendly", - "GET", - None, - &token2, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 1, - "Friend should find friends thought" - ); - - let response = make_jwt_request( - app.router.clone(), - "/search?q=friendly", - "GET", - None, - &token3, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 0, - "Stranger should NOT find friends thought" - ); - - let response = make_get_request(app.router.clone(), "/search?q=friendly", None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 0, - "Guest should NOT find friends thought" - ); - - // -- Thought Search (Private) -- - let response = make_jwt_request( - app.router.clone(), - "/search?q=private", - "GET", - None, - &token1, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 1, - "Author should find private thought" - ); - - let response = make_jwt_request( - app.router.clone(), - "/search?q=private", - "GET", - None, - &token2, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 0, - "Friend should NOT find private thought" - ); - - let response = make_get_request(app.router.clone(), "/search?q=private", None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - v["thoughts"]["thoughts"].as_array().unwrap().len(), - 0, - "Guest should NOT find private thought" - ); -} diff --git a/thoughts-backend/tests/api/tag.rs b/thoughts-backend/tests/api/tag.rs deleted file mode 100644 index e68bcd2..0000000 --- a/thoughts-backend/tests/api/tag.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::api::main::{create_user_with_password, login_user, setup, TestApp}; -use axum::http::StatusCode; -use http_body_util::BodyExt; -use serde_json::{json, Value}; -use utils::testing::{make_get_request, make_jwt_request}; - -#[tokio::test] -async fn test_hashtag_flow() { - let app = setup().await; - let user = - create_user_with_password(&app.db, "taguser", "password123", "taguser@example.com").await; - let token = login_user(app.router.clone(), "taguser", "password123").await; - - // 1. Post a thought with hashtags - let body = json!({ "content": "Hello #world this is a post about #RustLang" }).to_string(); - let response = - make_jwt_request(app.router.clone(), "/thoughts", "POST", Some(body), &token).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body_bytes = response.into_body().collect().await.unwrap().to_bytes(); - let thought_json: Value = serde_json::from_slice(&body_bytes).unwrap(); - let thought_id = thought_json["id"].as_str().unwrap(); - - // 2. Post another thought - let body2 = json!({ "content": "Another post about the #rustlang ecosystem" }).to_string(); - make_jwt_request(app.router.clone(), "/thoughts", "POST", Some(body2), &token).await; - - // 3. Fetch thoughts by tag "rustlang" - let response = make_get_request(app.router.clone(), "/tags/rustlang", Some(user.id)).await; - assert_eq!(response.status(), StatusCode::OK); - let body_bytes = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body_bytes).unwrap(); - - let thoughts = v["thoughts"].as_array().unwrap(); - assert_eq!(thoughts.len(), 2); - // Note: The most recent post appears first - assert_eq!( - thoughts[0]["content"], - "Another post about the #rustlang ecosystem" - ); - assert_eq!(thoughts[1]["id"], thought_id); - - // 4. Fetch thoughts by tag "world" - let response = make_get_request(app.router.clone(), "/tags/world", Some(user.id)).await; - assert_eq!(response.status(), StatusCode::OK); - let body_bytes = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body_bytes).unwrap(); - - let thoughts = v["thoughts"].as_array().unwrap(); - assert_eq!(thoughts.len(), 1); - assert_eq!(thoughts[0]["id"], thought_id); -} - -#[tokio::test] -async fn test_popular_tags() { - let app = setup().await; - let _ = create_user_with_password(&app.db, "poptag_user", "password123", "poptag@example.com") - .await; - let token = login_user(app.router.clone(), "poptag_user", "password123").await; - - // Helper async function to post a thought - async fn post_thought(app: &TestApp, token: &str, content: &str) { - let body = json!({ "content": content }).to_string(); - let response = - make_jwt_request(app.router.clone(), "/thoughts", "POST", Some(body), token).await; - assert_eq!(response.status(), StatusCode::CREATED); - } - - // 1. Post thoughts to create tag usage data - // Expected counts: rust (3), web (2), axum (2), testing (1) - post_thought(&app, &token, "My first post about #rust and the #web").await; - post_thought(&app, &token, "Another post about #rust and #axum").await; - post_thought(&app, &token, "I'm really enjoying #rust lately").await; - post_thought(&app, &token, "Let's talk about #axum and the #web").await; - post_thought(&app, &token, "Don't forget about #testing").await; - - // 2. Fetch the popular tags - let response = make_get_request(app.router.clone(), "/tags/popular", None).await; - println!("Response: {:?}", response); - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Vec = serde_json::from_slice(&body).unwrap(); - - // 3. Assert the results - assert_eq!(v.len(), 4, "Should return the 4 unique tags used"); - assert_eq!( - v, - vec!["rust", "axum", "web", "testing"], - "Tags should be ordered by popularity, then alphabetically" - ); -} diff --git a/thoughts-backend/tests/api/thought.rs b/thoughts-backend/tests/api/thought.rs deleted file mode 100644 index 36172c6..0000000 --- a/thoughts-backend/tests/api/thought.rs +++ /dev/null @@ -1,321 +0,0 @@ -use crate::api::main::{create_user_with_password, login_user}; - -use super::main::setup; -use app::persistence::follow; -use axum::{http::StatusCode, Router}; -use http_body_util::BodyExt; -use sea_orm::prelude::Uuid; -use serde_json::{json, Value}; -use utils::testing::{make_delete_request, make_get_request, make_jwt_request, make_post_request}; - -#[tokio::test] -async fn test_thought_endpoints() { - let app = setup().await; - let user1 = - create_user_with_password(&app.db, "user1", "password123", "user1@example.com").await; // AuthUser is ID 1 - let _user2 = - create_user_with_password(&app.db, "user2", "password123", "user2@example.com").await; // Other user is ID 2 - - // 1. Post a new thought as user 1 - let body = json!({ "content": "My first thought!" }).to_string(); - let response = make_post_request(app.router.clone(), "/thoughts", body, Some(user1.id)).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["content"], "My first thought!"); - assert_eq!(v["authorUsername"], "user1"); - let thought_id = v["id"].as_str().unwrap().to_string(); - - // 2. Post a thought with invalid content - let body = json!({ "content": "" }).to_string(); // Too short - let response = make_post_request(app.router.clone(), "/thoughts", body, Some(user1.id)).await; - assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); - - // 3. Attempt to delete another user's thought (user1 tries to delete a non-existent thought, but let's pretend it's user2's) - let response = make_delete_request( - app.router.clone(), - &format!("/thoughts/{}", Uuid::new_v4()), - Some(user1.id), - ) - .await; - assert_eq!(response.status(), StatusCode::NOT_FOUND); - - // 4. Delete the thought created in step 1 - let response = make_delete_request( - app.router.clone(), - &format!("/thoughts/{}", thought_id), - Some(user1.id), - ) - .await; - assert_eq!(response.status(), StatusCode::NO_CONTENT); -} - -#[tokio::test] -async fn test_thought_replies() { - let app = setup().await; - let user1 = - create_user_with_password(&app.db, "user1", "password123", "user1@example.com").await; - let user2 = - create_user_with_password(&app.db, "user2", "password123", "user2@example.com").await; - - // 1. User 1 posts an original thought - let body = json!({ "content": "This is the original post!" }).to_string(); - let response = make_post_request(app.router.clone(), "/thoughts", body, Some(user1.id)).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let original_thought: Value = serde_json::from_slice(&body).unwrap(); - let original_thought_id = original_thought["id"].as_str().unwrap(); - - // 2. User 2 replies to the original thought - let reply_body = json!({ - "content": "This is a reply.", - "replyToId": original_thought_id - }) - .to_string(); - let response = - make_post_request(app.router.clone(), "/thoughts", reply_body, Some(user2.id)).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let reply_thought: Value = serde_json::from_slice(&body).unwrap(); - - // 3. Verify the reply is linked correctly - assert_eq!(reply_thought["replyToId"], original_thought_id); - assert_eq!(reply_thought["authorUsername"], "user2"); -} - -#[tokio::test] -async fn test_thought_visibility() { - let app = setup().await; - let author = create_user_with_password(&app.db, "author", "password123", "a@a.com").await; - let friend = create_user_with_password(&app.db, "friend", "password123", "f@f.com").await; - let _stranger = create_user_with_password(&app.db, "stranger", "password123", "s@s.com").await; - - // Make author and friend follow each other - follow::follow_user(&app.db, author.id, friend.id) - .await - .unwrap(); - follow::follow_user(&app.db, friend.id, author.id) - .await - .unwrap(); - - let author_jwt = login_user(app.router.clone(), "author", "password123").await; - let friend_jwt = login_user(app.router.clone(), "friend", "password123").await; - let stranger_jwt = login_user(app.router.clone(), "stranger", "password123").await; - - // Author posts one of each visibility - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(json!({"content": "public", "visibility": "Public"}).to_string()), - &author_jwt, - ) - .await; - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(json!({"content": "friends", "visibility": "FriendsOnly"}).to_string()), - &author_jwt, - ) - .await; - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(json!({"content": "private", "visibility": "Private"}).to_string()), - &author_jwt, - ) - .await; - - // Helper to get thoughts and count them - async fn get_thought_count(router: Router, jwt: Option<&str>) -> usize { - let response = if let Some(token) = jwt { - make_jwt_request(router, "/users/author/thoughts", "GET", None, token).await - } else { - make_get_request(router, "/users/author/thoughts", None).await - }; - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - v["thoughts"].as_array().unwrap().len() - } - - // Assertions - assert_eq!( - get_thought_count(app.router.clone(), Some(&author_jwt)).await, - 3, - "Author should see all their posts" - ); - assert_eq!( - get_thought_count(app.router.clone(), Some(&friend_jwt)).await, - 2, - "Friend should see public and friends_only posts" - ); - assert_eq!( - get_thought_count(app.router.clone(), Some(&stranger_jwt)).await, - 1, - "Stranger should see only public posts" - ); - assert_eq!( - get_thought_count(app.router.clone(), None).await, - 1, - "Unauthenticated guest should see only public posts" - ); -} - -async fn post_thought_and_get_id( - router: &Router, - content: &str, - visibility: &str, - token: &str, -) -> String { - let body = json!({ "content": content, "visibility": visibility }).to_string(); - let response = make_jwt_request(router.clone(), "/thoughts", "POST", Some(body), token).await; - assert_eq!(response.status(), StatusCode::CREATED); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - v["id"].as_str().unwrap().to_string() -} - -#[tokio::test] -async fn test_get_thought_by_id_visibility() { - let app = setup().await; - let author = create_user_with_password(&app.db, "author", "password123", "a@a.com").await; - let friend = create_user_with_password(&app.db, "friend", "password123", "f@f.com").await; - let _stranger = create_user_with_password(&app.db, "stranger", "password123", "s@s.com").await; - - // Make author and friend follow each other - follow::follow_user(&app.db, author.id, friend.id) - .await - .unwrap(); - follow::follow_user(&app.db, friend.id, author.id) - .await - .unwrap(); - - let author_jwt = login_user(app.router.clone(), "author", "password123").await; - let friend_jwt = login_user(app.router.clone(), "friend", "password123").await; - let stranger_jwt = login_user(app.router.clone(), "stranger", "password123").await; - - // Author posts one of each visibility - let public_id = post_thought_and_get_id(&app.router, "public", "Public", &author_jwt).await; - let friends_id = - post_thought_and_get_id(&app.router, "friends", "FriendsOnly", &author_jwt).await; - let private_id = post_thought_and_get_id(&app.router, "private", "Private", &author_jwt).await; - - // --- Test Assertions --- - - // 1. Public thought - let public_url = format!("/thoughts/{}", public_id); - assert_eq!( - make_get_request(app.router.clone(), &public_url, None) - .await - .status(), - StatusCode::OK, - "Guest should see public thought" - ); - - // 2. Friends-only thought - let friends_url = format!("/thoughts/{}", friends_id); - assert_eq!( - make_jwt_request(app.router.clone(), &friends_url, "GET", None, &friend_jwt) - .await - .status(), - StatusCode::OK, - "Friend should see friends-only thought" - ); - assert_eq!( - make_jwt_request(app.router.clone(), &friends_url, "GET", None, &stranger_jwt) - .await - .status(), - StatusCode::NOT_FOUND, - "Stranger should NOT see friends-only thought" - ); - - // 3. Private thought - let private_url = format!("/thoughts/{}", private_id); - assert_eq!( - make_jwt_request(app.router.clone(), &private_url, "GET", None, &author_jwt) - .await - .status(), - StatusCode::OK, - "Author should see their private thought" - ); - assert_eq!( - make_jwt_request(app.router.clone(), &private_url, "GET", None, &friend_jwt) - .await - .status(), - StatusCode::NOT_FOUND, - "Friend should NOT see private thought" - ); -} - -#[tokio::test] -async fn test_get_thought_thread() { - let app = setup().await; - let _user1 = - create_user_with_password(&app.db, "user1", "password123", "user1@example.com").await; - let _user2 = - create_user_with_password(&app.db, "user2", "password123", "user2@example.com").await; - let user3 = - create_user_with_password(&app.db, "user3", "password123", "user3@example.com").await; - - let token1 = login_user(app.router.clone(), "user1", "password123").await; - let token2 = login_user(app.router.clone(), "user2", "password123").await; - - // 1. user1 posts a root thought - let root_id = post_thought_and_get_id(&app.router, "Root thought", "Public", &token1).await; - - // 2. user2 replies to the root thought - let reply1_body = json!({ "content": "First reply", "replyToId": root_id }).to_string(); - let response = make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(reply1_body), - &token2, - ) - .await; - let body = response.into_body().collect().await.unwrap().to_bytes(); - let reply1: Value = serde_json::from_slice(&body).unwrap(); - let reply1_id = reply1["id"].as_str().unwrap().to_string(); - - // 3. user1 replies to user2's reply - let reply2_body = - json!({ "content": "Reply to the reply", "replyToId": reply1_id }).to_string(); - make_jwt_request( - app.router.clone(), - "/thoughts", - "POST", - Some(reply2_body), - &token1, - ) - .await; - - // 4. Fetch the entire thread - let response = make_get_request( - app.router.clone(), - &format!("/thoughts/{}/thread", root_id), - Some(user3.id), // Fetch as a third user to test visibility - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let thread: Value = serde_json::from_slice(&body).unwrap(); - - // 5. Assert the structure - assert_eq!(thread["content"], "Root thought"); - assert_eq!(thread["authorUsername"], "user1"); - assert_eq!(thread["replies"].as_array().unwrap().len(), 1); - - let reply_level_1 = &thread["replies"][0]; - assert_eq!(reply_level_1["content"], "First reply"); - assert_eq!(reply_level_1["authorUsername"], "user2"); - assert_eq!(reply_level_1["replies"].as_array().unwrap().len(), 1); - - let reply_level_2 = &reply_level_1["replies"][0]; - assert_eq!(reply_level_2["content"], "Reply to the reply"); - assert_eq!(reply_level_2["authorUsername"], "user1"); - assert!(reply_level_2["replies"].as_array().unwrap().is_empty()); -} diff --git a/thoughts-backend/tests/api/user.rs b/thoughts-backend/tests/api/user.rs deleted file mode 100644 index 02f0166..0000000 --- a/thoughts-backend/tests/api/user.rs +++ /dev/null @@ -1,312 +0,0 @@ -use axum::http::StatusCode; -use http_body_util::BodyExt; -use models::domains::top_friends; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; -use serde_json::{json, Value}; - -use utils::testing::{make_get_request, make_jwt_request, make_post_request}; - -use crate::api::main::{create_user_with_password, login_user, setup}; - -#[tokio::test] -async fn test_post_users() { - let app = setup().await; - - let body = r#"{"username": "test", "email": "test@example.com", "password": "password123"}"# - .to_owned(); - let response = make_post_request(app.router, "/auth/register", body, None).await; - - assert_eq!(response.status(), StatusCode::CREATED); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - assert_eq!(v["username"], "test"); - assert!(v["displayName"].is_string()); -} - -#[tokio::test] -pub(super) async fn test_post_users_error() { - let app = setup().await; - - let body = - r#"{"username": "1", "email": "test@example.com", "password": "password123"}"#.to_owned(); - let response = make_post_request(app.router, "/auth/register", body, None).await; - - assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let result: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(result["message"], "Validation error"); - assert_eq!(result["details"]["username"][0]["code"], "length"); -} - -#[tokio::test] -pub async fn test_get_users() { - let app = setup().await; - - let body = r#"{"username": "test", "email": "test@example.com", "password": "password123"}"# - .to_owned(); - make_post_request(app.router.clone(), "/auth/register", body, None).await; - - let response = make_get_request(app.router, "/users", None).await; - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - assert!(v["users"].is_array()); - let users_array = v["users"].as_array().unwrap(); - assert_eq!(users_array.len(), 1); - assert_eq!(users_array[0]["username"], "test"); -} - -#[tokio::test] -async fn test_me_endpoints() { - let app = setup().await; - - // 1. Register a new user - let register_body = json!({ - "username": "me_user", - "email": "me_user@example.com", - "password": "password123" - }) - .to_string(); - let response = - make_post_request(app.router.clone(), "/auth/register", register_body, None).await; - assert_eq!(response.status(), StatusCode::CREATED); - - // 2. Log in to get a token - let token = login_user(app.router.clone(), "me_user", "password123").await; - - // 3. GET /users/me to fetch initial profile - let response = make_jwt_request(app.router.clone(), "/users/me", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v["username"], "me_user"); - assert!(v["bio"].is_null()); - assert!(v["displayName"].is_string()); - - // 4. PUT /users/me to update the profile - let update_body = json!({ - "displayName": "Me User", - "bio": "This is my updated bio.", - "avatarUrl": "https://example.com/avatar.png" - }) - .to_string(); - let response = make_jwt_request( - app.router.clone(), - "/users/me", - "PUT", - Some(update_body), - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v_updated: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v_updated["displayName"], "Me User"); - assert_eq!(v_updated["bio"], "This is my updated bio."); - - // 5. GET /users/me again to verify the update was persisted - let response = make_jwt_request(app.router.clone(), "/users/me", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v_verify: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(v_verify["displayName"], "Me User"); - assert_eq!(v_verify["bio"], "This is my updated bio."); -} - -#[tokio::test] -async fn test_update_me_top_friends() { - let app = setup().await; - - // 1. Create users for the test - let user_me = - create_user_with_password(&app.db, "me_user", "password123", "me_user@example.com").await; - let friend1 = - create_user_with_password(&app.db, "friend1", "password123", "friend1@example.com").await; - let friend2 = - create_user_with_password(&app.db, "friend2", "password123", "friend2@example.com").await; - let _friend3 = - create_user_with_password(&app.db, "friend3", "password123", "friend3@example.com").await; - - // 2. Log in as "me_user" - let token = login_user(app.router.clone(), "me_user", "password123").await; - - // 3. Update profile to set top friends - let update_body = json!({ - "topFriends": ["friend1", "friend2"] - }) - .to_string(); - - let response = make_jwt_request( - app.router.clone(), - "/users/me", - "PUT", - Some(update_body), - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - - // 4. Verify the database state directly - let top_friends_list = top_friends::Entity::find() - .filter(top_friends::Column::UserId.eq(user_me.id)) - .all(&app.db) - .await - .unwrap(); - - assert_eq!(top_friends_list.len(), 2); - assert_eq!(top_friends_list[0].friend_id, friend1.id); - assert_eq!(top_friends_list[0].position, 1); - assert_eq!(top_friends_list[1].friend_id, friend2.id); - assert_eq!(top_friends_list[1].position, 2); - - // 5. Update again with a different list to test replacement - let update_body_2 = json!({ - "topFriends": ["friend2"] - }) - .to_string(); - - let response = make_jwt_request( - app.router.clone(), - "/users/me", - "PUT", - Some(update_body_2), - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - - // 6. Verify the new state - let top_friends_list_2 = top_friends::Entity::find() - .filter(top_friends::Column::UserId.eq(user_me.id)) - .all(&app.db) - .await - .unwrap(); - - assert_eq!(top_friends_list_2.len(), 1); - assert_eq!(top_friends_list_2[0].friend_id, friend2.id); - assert_eq!(top_friends_list_2[0].position, 1); -} - -#[tokio::test] -async fn test_update_me_css_and_images() { - let app = setup().await; - - // 1. Create and log in as a user - let _ = - create_user_with_password(&app.db, "css_user", "password123", "css_user@example.com").await; - let token = login_user(app.router.clone(), "css_user", "password123").await; - - // 2. Attempt to update with an invalid avatar URL - let invalid_body = json!({ - "avatarUrl": "not-a-valid-url" - }) - .to_string(); - - let response = make_jwt_request( - app.router.clone(), - "/users/me", - "PUT", - Some(invalid_body), - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); - - // 3. Update profile with valid URLs and custom CSS - let valid_body = json!({ - "avatarUrl": "https://example.com/new-avatar.png", - "headerUrl": "https://example.com/new-header.jpg", - "customCss": "body { color: blue; }" - }) - .to_string(); - - let response = make_jwt_request( - app.router.clone(), - "/users/me", - "PUT", - Some(valid_body), - &token, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - - // 4. Verify the changes were persisted by fetching the profile again - let response = make_jwt_request(app.router.clone(), "/users/me", "GET", None, &token).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - assert_eq!(v["avatarUrl"], "https://example.com/new-avatar.png"); - assert_eq!(v["headerUrl"], "https://example.com/new-header.jpg"); - assert_eq!(v["customCss"], "body { color: blue; }"); -} - -#[tokio::test] -async fn test_get_all_users_paginated() { - let app = setup().await; - - for i in 0..25 { - create_user_with_password( - &app.db, - &format!("user{}", i), - "password123", - &format!("u{}@e.com", i), - ) - .await; - } - - let response_p1 = make_get_request(app.router.clone(), "/users/all", None).await; - assert_eq!(response_p1.status(), StatusCode::OK); - let body_p1 = response_p1.into_body().collect().await.unwrap().to_bytes(); - let v_p1: Value = serde_json::from_slice(&body_p1).unwrap(); - - assert_eq!( - v_p1["items"].as_array().unwrap().len(), - 20, - "First page should have 20 items" - ); - assert_eq!(v_p1["page"], 1); - assert_eq!(v_p1["pageSize"], 20); - assert_eq!(v_p1["totalPages"], 2); - assert_eq!(v_p1["totalItems"], 25); - - let response_p2 = make_get_request(app.router.clone(), "/users/all?page=2", None).await; - assert_eq!(response_p2.status(), StatusCode::OK); - let body_p2 = response_p2.into_body().collect().await.unwrap().to_bytes(); - let v_p2: Value = serde_json::from_slice(&body_p2).unwrap(); - - assert_eq!( - v_p2["items"].as_array().unwrap().len(), - 5, - "Second page should have 5 items" - ); - assert_eq!(v_p2["page"], 2); - assert_eq!(v_p2["totalPages"], 2); -} - -#[tokio::test] -async fn test_get_all_users_count() { - let app = setup().await; - - for i in 0..25 { - create_user_with_password( - &app.db, - &format!("user{}", i), - "password123", - &format!("u{}@e.com", i), - ) - .await; - } - - let response = make_get_request(app.router.clone(), "/users/count", None).await; - assert_eq!(response.status(), StatusCode::OK); - let body = response.into_body().collect().await.unwrap().to_bytes(); - let v: Value = serde_json::from_slice(&body).unwrap(); - - assert_eq!(v["count"], 25); -} diff --git a/thoughts-backend/tests/app/mod.rs b/thoughts-backend/tests/app/mod.rs deleted file mode 100644 index 7c4e38d..0000000 --- a/thoughts-backend/tests/app/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod persistence; diff --git a/thoughts-backend/tests/app/persistence/mod.rs b/thoughts-backend/tests/app/persistence/mod.rs deleted file mode 100644 index b40a127..0000000 --- a/thoughts-backend/tests/app/persistence/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use utils::testing::setup_test_db; - -mod user; - -use user::test_user; - -#[tokio::test] -async fn user_main() { - std::env::set_var( - "MANAGEMENT_DATABASE_URL", - "postgres://postgres:postgres@localhost:5434/postgres", - ); - std::env::set_var( - "DATABASE_URL", - "postgres://postgres:postgres@localhost:5434/postgres", - ); - let db = setup_test_db().await.expect("Failed to set up test db"); - - test_user(&db).await; -} diff --git a/thoughts-backend/tests/app/persistence/user.rs b/thoughts-backend/tests/app/persistence/user.rs deleted file mode 100644 index 034af34..0000000 --- a/thoughts-backend/tests/app/persistence/user.rs +++ /dev/null @@ -1,18 +0,0 @@ -use sea_orm::{DatabaseConnection, TryIntoModel}; - -use app::persistence::user::create_user; -use models::params::user::CreateUserParams; - -pub(super) async fn test_user(db: &DatabaseConnection) { - let params = CreateUserParams { - username: "test".to_string(), - password: "password".to_string(), - }; - let user_model = create_user(db, params) - .await - .expect("Create user failed!") - .try_into_model() // Convert ActiveModel to Model for easier checks - .unwrap(); - - assert_eq!(user_model.username, "test"); -} diff --git a/thoughts-backend/tests/mod.rs b/thoughts-backend/tests/mod.rs deleted file mode 100644 index 778fa0e..0000000 --- a/thoughts-backend/tests/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod api; -mod app; diff --git a/thoughts-backend/utils/Cargo.toml b/thoughts-backend/utils/Cargo.toml deleted file mode 100644 index 041c4c9..0000000 --- a/thoughts-backend/utils/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "utils" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "utils" -path = "src/lib.rs" - -[dependencies] -migration = { path = "../migration" } -uuid = { version = "1.18.1", features = ["v4", "serde"] } -sea-orm = { version = "1.1.12", features = ["sqlx-sqlite", "sqlx-postgres"] } - -axum = { workspace = true } -tower = { workspace = true, features = ["util"] } - - -tokio = { workspace = true } diff --git a/thoughts-backend/utils/README.md b/thoughts-backend/utils/README.md deleted file mode 100644 index 1694935..0000000 --- a/thoughts-backend/utils/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# utils - -Some utility functions to facilitate development. diff --git a/thoughts-backend/utils/src/db.rs b/thoughts-backend/utils/src/db.rs deleted file mode 100644 index e574c40..0000000 --- a/thoughts-backend/utils/src/db.rs +++ /dev/null @@ -1,8 +0,0 @@ -use migration::{sea_orm::DatabaseConnection, DbErr, Migrator, MigratorTrait, SchemaManager}; - -pub async fn migrate(conn: &DatabaseConnection) -> Result<(), DbErr> { - let schema_manager = SchemaManager::new(conn); - Migrator::up(conn, None).await?; - assert!(schema_manager.has_table("user").await?); - Ok(()) -} diff --git a/thoughts-backend/utils/src/lib.rs b/thoughts-backend/utils/src/lib.rs deleted file mode 100644 index 1c79795..0000000 --- a/thoughts-backend/utils/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod db; -pub mod testing; - -pub use db::migrate; diff --git a/thoughts-backend/utils/src/testing/api/mod.rs b/thoughts-backend/utils/src/testing/api/mod.rs deleted file mode 100644 index 0333a97..0000000 --- a/thoughts-backend/utils/src/testing/api/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use axum::{ - body::Body, - http::{header, Request}, - response::Response, - Router, -}; -use tower::ServiceExt; -use uuid::Uuid; - -pub async fn make_get_request(app: Router, url: &str, user_id: Option) -> Response { - let mut builder = Request::builder() - .uri(url) - .header("Content-Type", "application/json"); - - if let Some(user_id) = user_id { - builder = builder.header("x-test-user-id", user_id.to_string()); - } - - app.oneshot(builder.body(Body::empty()).unwrap()) - .await - .unwrap() -} - -pub async fn make_post_request( - app: Router, - url: &str, - body: String, - user_id: Option, -) -> Response { - let mut builder = Request::builder() - .method("POST") - .uri(url) - .header("Content-Type", "application/json"); - - if let Some(user_id) = user_id { - builder = builder.header("x-test-user-id", user_id.to_string()); - } - - app.oneshot(builder.body(Body::from(body)).unwrap()) - .await - .unwrap() -} - -pub async fn make_delete_request(app: Router, url: &str, user_id: Option) -> Response { - let mut builder = Request::builder() - .method("DELETE") - .uri(url) - .header("Content-Type", "application/json"); - - if let Some(user_id) = user_id { - builder = builder.header("x-test-user-id", user_id.to_string()); - } - - app.oneshot(builder.body(Body::empty()).unwrap()) - .await - .unwrap() -} - -pub async fn make_jwt_request( - app: Router, - url: &str, - method: &str, - body: Option, - token: &str, -) -> Response { - let builder = Request::builder() - .method(method) - .uri(url) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", token)); - - let request_body = body.unwrap_or_default(); - app.oneshot(builder.body(Body::from(request_body)).unwrap()) - .await - .unwrap() -} - -pub async fn make_request_with_headers( - app: Router, - url: &str, - method: &str, - body: Option, - headers: Vec<(header::HeaderName, &str)>, -) -> Response { - let mut builder = Request::builder() - .method(method) - .uri(url) - .header("Content-Type", "application/json"); - - for (key, value) in headers { - builder = builder.header(key, value); - } - - let request_body = body.unwrap_or_default(); - app.oneshot(builder.body(Body::from(request_body)).unwrap()) - .await - .unwrap() -} diff --git a/thoughts-backend/utils/src/testing/db/mod.rs b/thoughts-backend/utils/src/testing/db/mod.rs deleted file mode 100644 index 11dff68..0000000 --- a/thoughts-backend/utils/src/testing/db/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr, Statement}; -use uuid::Uuid; - -use crate::migrate; - -pub async fn setup_test_db() -> Result { - let mgmt_db_url = std::env::var("MANAGEMENT_DATABASE_URL") - .expect("MANAGEMENT_DATABASE_URL must be set for tests"); - let db_name = format!("test_db_{}", Uuid::new_v4().simple()); - let (base_url, _) = mgmt_db_url - .rsplit_once('/') - .expect("MANAGEMENT_DATABASE_URL must include a database name, e.g., '/postgres'"); - - let db = Database::connect(&mgmt_db_url).await?; - db.execute(Statement::from_string( - DbBackend::Postgres, - format!(r#"CREATE DATABASE "{}";"#, db_name), - )) - .await?; - - // 2. Connect to the new test DB and run migrations - let new_db_url = format!("{}/{}", base_url, db_name); - let conn = Database::connect(&new_db_url).await?; - migrate(&conn).await?; - - Ok(conn) -} diff --git a/thoughts-backend/utils/src/testing/mod.rs b/thoughts-backend/utils/src/testing/mod.rs deleted file mode 100644 index bb8a5ba..0000000 --- a/thoughts-backend/utils/src/testing/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod api; -mod db; - -pub use api::{ - make_delete_request, make_get_request, make_jwt_request, make_post_request, - make_request_with_headers, -}; -pub use db::setup_test_db;