added vibecoded swagger documentation with utoipa
This commit is contained in:
Generated
+223
-111
@@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -82,6 +88,15 @@ version = "1.0.103"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -128,9 +143,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.17.0"
|
version = "1.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -138,14 +153,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.41.0"
|
version = "0.42.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
"dunce",
|
"dunce",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -323,15 +339,15 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytesize"
|
name = "bytesize"
|
||||||
version = "2.4.0"
|
version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49e78e506b9d7633710dab98996f22f95f3d0f488e8f1aa162830556ed9fc14d"
|
checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.65"
|
version = "1.2.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -542,6 +558,15 @@ version = "2.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@@ -613,6 +638,17 @@ version = "0.5.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diff"
|
name = "diff"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@@ -814,6 +850,16 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||||
|
dependencies = [
|
||||||
|
"miniz_oxide",
|
||||||
|
"zlib-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -906,6 +952,8 @@ dependencies = [
|
|||||||
"tower_governor",
|
"tower_governor",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-swagger-ui",
|
||||||
"uuid",
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
@@ -1037,9 +1085,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
"rand_core 0.10.1",
|
"rand_core 0.10.1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1460,6 +1510,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.17.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1549,11 +1601,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.4.3",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1606,14 +1658,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.17"
|
version = "0.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
"plain",
|
"plain",
|
||||||
"redox_syscall 0.8.1",
|
"redox_syscall 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1706,6 +1758,16 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -1788,9 +1850,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -2133,15 +2195,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-proto"
|
name = "quinn-proto"
|
||||||
version = "0.11.15"
|
version = "0.11.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
|
checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"bytes",
|
"bytes",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.4.3",
|
||||||
"lru-slab",
|
"lru-slab",
|
||||||
"rand 0.9.4",
|
"rand 0.10.2",
|
||||||
|
"rand_pcg",
|
||||||
"ring",
|
"ring",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
@@ -2155,16 +2218,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-udp"
|
name = "quinn-udp"
|
||||||
version = "0.5.14"
|
version = "0.5.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2211,9 +2274,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chacha20",
|
"chacha20",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
@@ -2264,6 +2327,15 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_pcg"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-cpuid"
|
name = "raw-cpuid"
|
||||||
version = "11.6.0"
|
version = "11.6.0"
|
||||||
@@ -2284,9 +2356,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.8.1"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7"
|
checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
@@ -2403,6 +2475,40 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "8.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "8.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "8.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||||
|
dependencies = [
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-multipart-rfc7578_2"
|
name = "rust-multipart-rfc7578_2"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2414,15 +2520,15 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"mime",
|
"mime",
|
||||||
"rand 0.10.1",
|
"rand 0.10.2",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.2"
|
version = "2.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
@@ -2474,9 +2580,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.14.1"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"web-time",
|
"web-time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -2710,6 +2816,12 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd_cesu8"
|
name = "simd_cesu8"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -3133,9 +3245,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.51"
|
version = "0.3.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
|
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
@@ -3153,9 +3265,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.30"
|
version = "0.2.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
|
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
@@ -3546,6 +3658,48 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa"
|
||||||
|
version = "5.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"utoipa-gen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-gen"
|
||||||
|
version = "5.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
"url",
|
||||||
|
"utoipa",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.23.4"
|
version = "1.23.4"
|
||||||
@@ -3844,15 +3998,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -3886,30 +4031,13 @@ dependencies = [
|
|||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc 0.52.6",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu 0.52.6",
|
||||||
"windows_i686_gnullvm 0.52.6",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc 0.52.6",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu 0.52.6",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.53.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
|
||||||
"windows_aarch64_msvc 0.53.1",
|
|
||||||
"windows_i686_gnu 0.53.1",
|
|
||||||
"windows_i686_gnullvm 0.53.1",
|
|
||||||
"windows_i686_msvc 0.53.1",
|
|
||||||
"windows_x86_64_gnu 0.53.1",
|
|
||||||
"windows_x86_64_gnullvm 0.53.1",
|
|
||||||
"windows_x86_64_msvc 0.53.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -3922,12 +4050,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -3940,12 +4062,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -3958,24 +4074,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -3988,12 +4092,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -4006,12 +4104,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -4024,12 +4116,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@@ -4042,12 +4128,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.57.1"
|
version = "0.57.1"
|
||||||
@@ -4169,8 +4249,40 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zopfli"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"crc32fast",
|
||||||
|
"log",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ tower-http = { version = "0.6.6", features = ["cors", "limit", "trace", "fs"] }
|
|||||||
tower_governor = "0.8.0"
|
tower_governor = "0.8.0"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
||||||
|
utoipa = { version = "5.5.0", features = ["uuid"] }
|
||||||
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||||
uuid = { version = "1.19.0", features = ["serde", "v7"] }
|
uuid = { version = "1.19.0", features = ["serde", "v7"] }
|
||||||
validator = "0.20.0"
|
validator = "0.20.0"
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,16 @@
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- Specify the JWT secret with the `FRANGIPANE_JWT_SECRET` environment variable.
|
- Specify the JWT secret with the `FRANGIPANE_JWT_SECRET` environment variable.
|
||||||
|
|
||||||
|
## AI usage
|
||||||
|
|
||||||
|
Here is a list of things written by AI in this project:
|
||||||
|
- All the Utoipa annotations for the Swagger documentation
|
||||||
|
- File upload logic
|
||||||
|
- Voice chat audio handling
|
||||||
|
|
||||||
|
Everything else is human-made.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The API serves a Swagger UI at `/swagger-ui`.
|
||||||
|
|||||||
+12
-1
@@ -7,7 +7,7 @@ use password_hash::rand_core::OsRng;
|
|||||||
use axum::{Json, http::HeaderMap};
|
use axum::{Json, http::HeaderMap};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::errors::APIError;
|
use crate::errors::{APIError, ErrorResponse};
|
||||||
|
|
||||||
const DEFAULT_SECRET_KEY: &str = "43aaf85b92f1ae6fbcef7732c50a0904";
|
const DEFAULT_SECRET_KEY: &str = "43aaf85b92f1ae6fbcef7732c50a0904";
|
||||||
|
|
||||||
@@ -83,6 +83,17 @@ pub fn verify_jwt_string(token: &String) -> Result<Claims, APIError> {
|
|||||||
.map_err(|_| APIError::InvalidToken)
|
.map_err(|_| APIError::InvalidToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/validate-token",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Token is valid", body = serde_json::Value),
|
||||||
|
(status = 401, description = "Missing or invalid token", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn validate_token(headers: HeaderMap) -> Result<Json<serde_json::Value>, APIError> {
|
pub async fn validate_token(headers: HeaderMap) -> Result<Json<serde_json::Value>, APIError> {
|
||||||
let _ = verify_jwt(headers)?;
|
let _ = verify_jwt(headers)?;
|
||||||
Ok(Json(serde_json::json!({"valid": true})))
|
Ok(Json(serde_json::json!({"valid": true})))
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ use serde_json::json;
|
|||||||
|
|
||||||
use crate::{MAX_ATTACHMENTS_PER_MESSAGE, MAX_ROOM_NAME_LENGTH, MAX_USERNAME_LENGTH};
|
use crate::{MAX_ATTACHMENTS_PER_MESSAGE, MAX_ROOM_NAME_LENGTH, MAX_USERNAME_LENGTH};
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||||
|
pub struct ErrorResponse {
|
||||||
|
pub code: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum APIError {
|
pub enum APIError {
|
||||||
// Auth Errors
|
// Auth Errors
|
||||||
|
|||||||
+89
@@ -21,6 +21,8 @@ use tower_http::{
|
|||||||
trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
|
trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
|
||||||
};
|
};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
|
||||||
const MAX_USERNAME_LENGTH: usize = 35;
|
const MAX_USERNAME_LENGTH: usize = 35;
|
||||||
const MAX_ROOM_NAME_LENGTH: usize = 35;
|
const MAX_ROOM_NAME_LENGTH: usize = 35;
|
||||||
@@ -62,6 +64,92 @@ pub struct Cli {
|
|||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Define the OpenAPI specification document structure
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
auth::validate_token,
|
||||||
|
routes::friends::list_friends,
|
||||||
|
routes::friends::list_requests,
|
||||||
|
routes::friends::send_request,
|
||||||
|
routes::friends::accept_request,
|
||||||
|
routes::friends::decline_request,
|
||||||
|
routes::friends::is_friend,
|
||||||
|
routes::friends::remove_friend,
|
||||||
|
routes::messages::list_messages,
|
||||||
|
routes::messages::create_message,
|
||||||
|
routes::messages::message_ws_handler,
|
||||||
|
routes::messages::create_message_with_attachments,
|
||||||
|
routes::messages::get_upload,
|
||||||
|
routes::rooms::list_rooms,
|
||||||
|
routes::rooms::create_room,
|
||||||
|
routes::rooms::get_room,
|
||||||
|
routes::rooms::list_invites,
|
||||||
|
routes::rooms::send_invite,
|
||||||
|
routes::rooms::accept_request,
|
||||||
|
routes::rooms::decline_request,
|
||||||
|
routes::rooms::leave_room,
|
||||||
|
routes::rooms::transfer_ownership,
|
||||||
|
routes::rooms::list_members,
|
||||||
|
routes::rooms::delete_room,
|
||||||
|
routes::users::login,
|
||||||
|
routes::users::register_user,
|
||||||
|
routes::users::update_user,
|
||||||
|
routes::users::upload_avatar,
|
||||||
|
routes::users::get_avatar,
|
||||||
|
routes::voice::voice_ws_handler,
|
||||||
|
routes::ws::issue_ws_token,
|
||||||
|
),
|
||||||
|
components(
|
||||||
|
schemas(
|
||||||
|
errors::ErrorResponse,
|
||||||
|
routes::friends::Friend,
|
||||||
|
routes::friends::FriendRequest,
|
||||||
|
routes::friends::SendFriendRequestPayload,
|
||||||
|
routes::friends::AcceptFriendRequestPayload,
|
||||||
|
routes::friends::DeclineFriendRequestPayload,
|
||||||
|
routes::friends::RemoveFriendPayload,
|
||||||
|
routes::messages::Message,
|
||||||
|
routes::messages::Attachment,
|
||||||
|
routes::messages::NewMessagePayload,
|
||||||
|
routes::rooms::Room,
|
||||||
|
routes::rooms::NewRoomPayload,
|
||||||
|
routes::rooms::RoomInvite,
|
||||||
|
routes::rooms::SendRoomInvitePayload,
|
||||||
|
routes::rooms::AcceptRoomInvitePayload,
|
||||||
|
routes::rooms::TransferOwnershipPayload,
|
||||||
|
routes::users::User,
|
||||||
|
routes::users::UserProfile,
|
||||||
|
routes::users::LoginPayload,
|
||||||
|
routes::users::LoginResponse,
|
||||||
|
routes::users::NewUserPayload,
|
||||||
|
routes::users::UpdateUserPayoad,
|
||||||
|
routes::users::UpdateUserResponse,
|
||||||
|
routes::ws::WsAuthQuery,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
modifiers(&SecurityAddon)
|
||||||
|
)]
|
||||||
|
pub struct ApiDoc;
|
||||||
|
|
||||||
|
struct SecurityAddon;
|
||||||
|
|
||||||
|
impl utoipa::Modify for SecurityAddon {
|
||||||
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
|
if let Some(components) = openapi.components.as_mut() {
|
||||||
|
components.add_security_scheme(
|
||||||
|
"bearer_auth",
|
||||||
|
utoipa::openapi::security::SecurityScheme::Http(
|
||||||
|
utoipa::openapi::security::HttpBuilder::new()
|
||||||
|
.scheme(utoipa::openapi::security::HttpAuthScheme::Bearer)
|
||||||
|
.bearer_format("JWT")
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the app with all routes, middleware and extensions
|
/// Create the app with all routes, middleware and extensions
|
||||||
pub fn create_app(
|
pub fn create_app(
|
||||||
db_pool: sqlx::PgPool,
|
db_pool: sqlx::PgPool,
|
||||||
@@ -99,6 +187,7 @@ pub fn create_app(
|
|||||||
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]);
|
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]);
|
||||||
|
|
||||||
let mut router = Router::new()
|
let mut router = Router::new()
|
||||||
|
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
|
||||||
.route("/version", get(get_version))
|
.route("/version", get(get_version))
|
||||||
.merge(routes::users::routes())
|
.merge(routes::users::routes())
|
||||||
.merge(routes::rooms::routes())
|
.merge(routes::rooms::routes())
|
||||||
|
|||||||
+108
-7
@@ -10,37 +10,37 @@ use uuid::Uuid;
|
|||||||
use crate::{auth::verify_jwt, db::id_from_username};
|
use crate::{auth::verify_jwt, db::id_from_username};
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{user_id_from_uuid, username_from_id, username_from_uuid},
|
db::{user_id_from_uuid, username_from_id, username_from_uuid},
|
||||||
errors::APIError,
|
errors::{APIError, ErrorResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
|
||||||
pub struct Friend {
|
pub struct Friend {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
|
||||||
pub struct FriendRequest {
|
pub struct FriendRequest {
|
||||||
pub sender_uuid: Uuid,
|
pub sender_uuid: Uuid,
|
||||||
pub sender_username: String,
|
pub sender_username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct SendFriendRequestPayload {
|
pub struct SendFriendRequestPayload {
|
||||||
pub receiver_username: String,
|
pub receiver_username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct AcceptFriendRequestPayload {
|
pub struct AcceptFriendRequestPayload {
|
||||||
pub sender_uuid: Uuid,
|
pub sender_uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct DeclineFriendRequestPayload {
|
pub struct DeclineFriendRequestPayload {
|
||||||
pub sender_uuid: Uuid,
|
pub sender_uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct RemoveFriendPayload {
|
pub struct RemoveFriendPayload {
|
||||||
pub friend_uuid: Uuid,
|
pub friend_uuid: Uuid,
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,19 @@ pub fn routes() -> Router {
|
|||||||
.route("/friends/remove", post(remove_friend))
|
.route("/friends/remove", post(remove_friend))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/friends",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Friends list successfully retrieved", body = [Friend]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_friends(
|
async fn list_friends(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -80,6 +93,19 @@ async fn list_friends(
|
|||||||
Ok(Json(friends))
|
Ok(Json(friends))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/friends/requests",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Friend requests list successfully retrieved", body = [FriendRequest]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_requests(
|
async fn list_requests(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -103,6 +129,22 @@ async fn list_requests(
|
|||||||
Ok(Json(requests))
|
Ok(Json(requests))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/friends/request",
|
||||||
|
request_body = SendFriendRequestPayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Friend request sent successfully", body = FriendRequest),
|
||||||
|
(status = 400, description = "Cannot send friend request to yourself", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 409, description = "You are already friends or Request already pending", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -151,6 +193,21 @@ async fn send_request(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/friends/accept",
|
||||||
|
request_body = AcceptFriendRequestPayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Friend request accepted successfully", body = Friend),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Friend request not found", body = ErrorResponse),
|
||||||
|
(status = 409, description = "You are already friends", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn accept_request(
|
async fn accept_request(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -204,6 +261,20 @@ async fn accept_request(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/friends/decline",
|
||||||
|
request_body = DeclineFriendRequestPayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Friend request declined successfully"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Friend request not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn decline_request(
|
async fn decline_request(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -234,6 +305,22 @@ async fn decline_request(
|
|||||||
Ok(StatusCode::CREATED)
|
Ok(StatusCode::CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/friends/check/{uuid}",
|
||||||
|
params(
|
||||||
|
("uuid" = Uuid, Path, description = "Target user UUID to check friendship status")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Friendship status checked successfully", body = bool),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn is_friend(
|
async fn is_friend(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(target_uuid): Path<Uuid>,
|
Path(target_uuid): Path<Uuid>,
|
||||||
@@ -261,6 +348,20 @@ async fn is_friend(
|
|||||||
Ok(Json(is_friend))
|
Ok(Json(is_friend))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/friends/remove",
|
||||||
|
request_body = RemoveFriendPayload,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Friend removed successfully"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or User is not in your friends list", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn remove_friend(
|
async fn remove_friend(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
|
|||||||
+90
-15
@@ -21,7 +21,7 @@ use crate::{
|
|||||||
AppConfig,
|
AppConfig,
|
||||||
auth::{verify_jwt, verify_jwt_string},
|
auth::{verify_jwt, verify_jwt_string},
|
||||||
db::room_id_from_uuid,
|
db::room_id_from_uuid,
|
||||||
errors::APIError,
|
errors::{APIError, ErrorResponse},
|
||||||
routes::{rooms::is_member, ws::WsAuthQuery},
|
routes::{rooms::is_member, ws::WsAuthQuery},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -40,7 +40,7 @@ pub struct MessageRow {
|
|||||||
pub sent_at: chrono::NaiveDateTime,
|
pub sent_at: chrono::NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, Debug, Clone, serde::Deserialize)]
|
#[derive(serde::Serialize, Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub room_uuid: Uuid,
|
pub room_uuid: Uuid,
|
||||||
@@ -52,20 +52,20 @@ pub struct Message {
|
|||||||
pub sent_at: String,
|
pub sent_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||||
pub struct Attachment {
|
pub struct Attachment {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub file_type: String, // image, video, textfile, binaryfile
|
pub file_type: String, // image, video, textfile, binaryfile
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct NewMessagePayload {
|
pub struct NewMessagePayload {
|
||||||
pub message_type: String,
|
pub message_type: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::IntoParams)]
|
||||||
struct MessageFetchQuery {
|
struct MessageFetchQuery {
|
||||||
limit: Option<i32>,
|
limit: Option<i32>,
|
||||||
before: Option<Uuid>,
|
before: Option<Uuid>,
|
||||||
@@ -84,6 +84,24 @@ pub fn routes() -> Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Also resets `last_read_at`
|
/// Also resets `last_read_at`
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/messages/{room_uuid}",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to retrieve messages from"),
|
||||||
|
MessageFetchQuery
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Messages successfully retrieved", body = [Message]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_messages(
|
async fn list_messages(
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
Query(query): Query<MessageFetchQuery>,
|
Query(query): Query<MessageFetchQuery>,
|
||||||
@@ -103,7 +121,6 @@ async fn list_messages(
|
|||||||
|
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
|
|
||||||
// 1. Fetch the messages (Simple query)
|
|
||||||
let rows = sqlx::query_as::<_, MessageRow>(
|
let rows = sqlx::query_as::<_, MessageRow>(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@@ -129,11 +146,8 @@ async fn list_messages(
|
|||||||
.fetch_all(&mut *tx)
|
.fetch_all(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// 2. Collect all the UUIDs of the messages we just found
|
|
||||||
let message_uuids: Vec<Uuid> = rows.iter().map(|m| m.uuid).collect();
|
let message_uuids: Vec<Uuid> = rows.iter().map(|m| m.uuid).collect();
|
||||||
|
|
||||||
// 3. Fetch all attachments for these specific messages in one batch
|
|
||||||
// We use a temporary struct to capture the message_uuid link from the DB
|
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
struct AttachmentRow {
|
struct AttachmentRow {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
@@ -149,7 +163,6 @@ async fn list_messages(
|
|||||||
.fetch_all(&mut *tx)
|
.fetch_all(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// 4. Group the attachments by their message_uuid in a HashMap
|
|
||||||
let mut attachment_map: HashMap<Uuid, Vec<Attachment>> = HashMap::new();
|
let mut attachment_map: HashMap<Uuid, Vec<Attachment>> = HashMap::new();
|
||||||
for att in attachment_rows {
|
for att in attachment_rows {
|
||||||
attachment_map
|
attachment_map
|
||||||
@@ -162,7 +175,6 @@ async fn list_messages(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Map the MessageRow to the final Message struct, pulling from the HashMap
|
|
||||||
let mut messages: Vec<Message> = rows
|
let mut messages: Vec<Message> = rows
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|m| {
|
.map(|m| {
|
||||||
@@ -174,7 +186,7 @@ async fn list_messages(
|
|||||||
sender_uuid: m.sender_uuid,
|
sender_uuid: m.sender_uuid,
|
||||||
message_type: m.message_type,
|
message_type: m.message_type,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
attachments, // Populated from HashMap
|
attachments,
|
||||||
sent_at: m.sent_at.format("%Y-%m-%d %H:%M:%S").to_string(),
|
sent_at: m.sent_at.format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -182,7 +194,7 @@ async fn list_messages(
|
|||||||
|
|
||||||
messages.reverse();
|
messages.reverse();
|
||||||
|
|
||||||
// Reset last_read_at
|
// Reset `last_read_at`
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"UPDATE membership_ SET last_read_at = CURRENT_TIMESTAMP WHERE user_id = $1 AND room = $2",
|
"UPDATE membership_ SET last_read_at = CURRENT_TIMESTAMP WHERE user_id = $1 AND room = $2",
|
||||||
)
|
)
|
||||||
@@ -196,6 +208,24 @@ async fn list_messages(
|
|||||||
Ok(Json(messages))
|
Ok(Json(messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/messages/{room_uuid}",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to create the message in")
|
||||||
|
),
|
||||||
|
request_body = NewMessagePayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Message created successfully", body = Message),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn create_message(
|
async fn create_message(
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -261,6 +291,18 @@ async fn create_message(
|
|||||||
Ok((StatusCode::CREATED, Json(message)))
|
Ok((StatusCode::CREATED, Json(message)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/ws/messages",
|
||||||
|
params(
|
||||||
|
WsAuthQuery
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 101, description = "WebSocket upgrade handshake accepted and connection initiated"),
|
||||||
|
(status = 401, description = "Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn message_ws_handler(
|
async fn message_ws_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||||
@@ -284,8 +326,10 @@ async fn message_ws_handler(
|
|||||||
.bind(query.token)
|
.bind(query.token)
|
||||||
.execute(&db)
|
.execute(&db)
|
||||||
.await
|
.await
|
||||||
// NOTE: Maybe wrong type of error
|
.map_err(|e| {
|
||||||
.map_err(|e| APIError::Internal(format!("Failed to get WS token from DB: {e}")))?;
|
tracing::debug!("Failed to get WS token from DB: {e}");
|
||||||
|
APIError::Internal(format!("Failed to get WS token from DB"))
|
||||||
|
})?;
|
||||||
|
|
||||||
if result.rows_affected() == 0 {
|
if result.rows_affected() == 0 {
|
||||||
return Err(APIError::InvalidToken);
|
return Err(APIError::InvalidToken);
|
||||||
@@ -362,6 +406,26 @@ async fn handle_message_socket(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/messages/{room_uuid}/upload",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to upload attachments to")
|
||||||
|
),
|
||||||
|
request_body(content = String, content_type = "multipart/form-data", description = "Multipart form with message payload fields: content and files"),
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Message with attachments created successfully", body = Message),
|
||||||
|
(status = 400, description = "Wrong file format or Maximum 8 files allowed", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 413, description = "One or more files exceed the size limit", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn create_message_with_attachments(
|
async fn create_message_with_attachments(
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -507,6 +571,17 @@ async fn create_message_with_attachments(
|
|||||||
Ok((StatusCode::CREATED, Json(message)))
|
Ok((StatusCode::CREATED, Json(message)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/uploads/{file_uuid}",
|
||||||
|
params(
|
||||||
|
("file_uuid" = Uuid, Path, description = "UUID of the file attachment")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "File served successfully"),
|
||||||
|
(status = 500, description = "File not found in DB, missing on disk, or file service error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn get_upload(
|
async fn get_upload(
|
||||||
Path(file_uuid): Path<Uuid>,
|
Path(file_uuid): Path<Uuid>,
|
||||||
Extension(config): Extension<Arc<AppConfig>>,
|
Extension(config): Extension<Arc<AppConfig>>,
|
||||||
|
|||||||
+176
-6
@@ -10,10 +10,11 @@ use uuid::Uuid;
|
|||||||
use crate::{MAX_ROOM_NAME_LENGTH, auth::verify_jwt, db::room_id_from_uuid, errors::APIError};
|
use crate::{MAX_ROOM_NAME_LENGTH, auth::verify_jwt, db::room_id_from_uuid, errors::APIError};
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{id_from_username, room_name_from_uuid, user_id_from_uuid, username_from_id},
|
db::{id_from_username, room_name_from_uuid, user_id_from_uuid, username_from_id},
|
||||||
|
errors::ErrorResponse,
|
||||||
routes::users::UserProfile,
|
routes::users::UserProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize, serde::Deserialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -23,13 +24,13 @@ pub struct Room {
|
|||||||
pub unread_count: i64,
|
pub unread_count: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct NewRoomPayload {
|
pub struct NewRoomPayload {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub global: bool,
|
pub global: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
|
||||||
pub struct RoomInvite {
|
pub struct RoomInvite {
|
||||||
pub room_uuid: Uuid,
|
pub room_uuid: Uuid,
|
||||||
pub room_name: String,
|
pub room_name: String,
|
||||||
@@ -37,19 +38,19 @@ pub struct RoomInvite {
|
|||||||
pub sender_username: String,
|
pub sender_username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct SendRoomInvitePayload {
|
pub struct SendRoomInvitePayload {
|
||||||
pub room_uuid: Uuid,
|
pub room_uuid: Uuid,
|
||||||
pub receiver_username: String,
|
pub receiver_username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct AcceptRoomInvitePayload {
|
pub struct AcceptRoomInvitePayload {
|
||||||
pub room_uuid: Uuid,
|
pub room_uuid: Uuid,
|
||||||
pub sender_uuid: Uuid,
|
pub sender_uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct TransferOwnershipPayload {
|
pub struct TransferOwnershipPayload {
|
||||||
pub room_uuid: Uuid,
|
pub room_uuid: Uuid,
|
||||||
pub new_owner_uuid: Uuid,
|
pub new_owner_uuid: Uuid,
|
||||||
@@ -90,6 +91,19 @@ pub async fn is_member(user_id: i32, room_id: i32, db: &Pool<Postgres>) -> bool
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/rooms",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Successfully listed rooms", body = [Room]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_rooms(
|
async fn list_rooms(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -130,6 +144,21 @@ async fn list_rooms(
|
|||||||
Ok(Json(rooms))
|
Ok(Json(rooms))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms",
|
||||||
|
request_body = NewRoomPayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Room successfully created", body = Room),
|
||||||
|
(status = 400, description = "Room name must be 0-35 characters long", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn create_room(
|
async fn create_room(
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -184,6 +213,23 @@ async fn create_room(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/rooms/{room_uuid}",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to retrieve")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Room successfully retrieved", body = Room),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn get_room(
|
async fn get_room(
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -246,6 +292,19 @@ async fn get_room(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/rooms/invites",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List of room invites successfully retrieved", body = [RoomInvite]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_invites(
|
async fn list_invites(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -273,6 +332,22 @@ async fn list_invites(
|
|||||||
Ok(Json(requests))
|
Ok(Json(requests))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms/invite",
|
||||||
|
request_body = SendRoomInvitePayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Room invite successfully sent", body = RoomInvite),
|
||||||
|
(status = 400, description = "Cannot invite yourself", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 409, description = "User is already a member or Invite already sent", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn send_invite(
|
async fn send_invite(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -327,6 +402,21 @@ async fn send_invite(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms/join",
|
||||||
|
request_body = AcceptRoomInvitePayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Room invite successfully accepted", body = Room),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found, Room not found, or Invite not found", body = ErrorResponse),
|
||||||
|
(status = 409, description = "User is already a member", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn accept_request(
|
async fn accept_request(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -399,6 +489,20 @@ async fn accept_request(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms/decline",
|
||||||
|
request_body = AcceptRoomInvitePayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Room invite successfully declined"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Invite not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn decline_request(
|
async fn decline_request(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -428,6 +532,23 @@ async fn decline_request(
|
|||||||
Ok(StatusCode::CREATED)
|
Ok(StatusCode::CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms/{room_uuid}/leave",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to leave")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Successfully left the room"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room or Owner cannot leave the room without transferring ownership", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn leave_room(
|
async fn leave_room(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
@@ -469,6 +590,21 @@ async fn leave_room(
|
|||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/rooms/transfer-ownership",
|
||||||
|
request_body = TransferOwnershipPayload,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Room ownership successfully transferred"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn transfer_ownership(
|
async fn transfer_ownership(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -515,6 +651,23 @@ async fn transfer_ownership(
|
|||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/rooms/{room_uuid}/members",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to list members of")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Room members successfully retrieved", body = [UserProfile]),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room or Cannot list members for global rooms", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn list_members(
|
async fn list_members(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
@@ -554,6 +707,23 @@ async fn list_members(
|
|||||||
Ok((StatusCode::OK, Json(members)))
|
Ok((StatusCode::OK, Json(members)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/rooms/{room_uuid}/delete",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room to delete")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Room successfully deleted"),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn delete_room(
|
async fn delete_room(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
|
|||||||
+73
-8
@@ -15,10 +15,10 @@ use crate::{
|
|||||||
AppConfig, MAX_UPLOAD_SIZE, MAX_USERNAME_LENGTH,
|
AppConfig, MAX_UPLOAD_SIZE, MAX_USERNAME_LENGTH,
|
||||||
auth::{create_jwt, hash_password, validate_token, verify_jwt, verify_password},
|
auth::{create_jwt, hash_password, validate_token, verify_jwt, verify_password},
|
||||||
db::{user_id_from_uuid, username_from_uuid},
|
db::{user_id_from_uuid, username_from_uuid},
|
||||||
errors::APIError,
|
errors::{APIError, ErrorResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@@ -26,19 +26,19 @@ pub struct User {
|
|||||||
pub email: String,
|
pub email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, utoipa::ToSchema)]
|
||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct LoginPayload {
|
pub struct LoginPayload {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@@ -46,21 +46,21 @@ pub struct LoginResponse {
|
|||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct NewUserPayload {
|
pub struct NewUserPayload {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct UpdateUserPayoad {
|
pub struct UpdateUserPayoad {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||||
pub struct UpdateUserResponse {
|
pub struct UpdateUserResponse {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@@ -88,6 +88,17 @@ async fn registration_guard(
|
|||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/login",
|
||||||
|
request_body = LoginPayload,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Logged in successfully", body = LoginResponse),
|
||||||
|
(status = 401, description = "Invalid email or password", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
Json(payload): Json<LoginPayload>,
|
Json(payload): Json<LoginPayload>,
|
||||||
@@ -123,6 +134,17 @@ pub async fn login(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/register",
|
||||||
|
request_body = NewUserPayload,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "User successfully registered", body = LoginResponse),
|
||||||
|
(status = 400, description = "Required fields are empty, Invalid email format, Username must be 1-35 characters long, or Password must be at least 8 characters", body = ErrorResponse),
|
||||||
|
(status = 409, description = "Username already taken or Email already in use", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error or Internal error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn register_user(
|
pub async fn register_user(
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
Json(payload): Json<NewUserPayload>,
|
Json(payload): Json<NewUserPayload>,
|
||||||
@@ -186,6 +208,22 @@ pub async fn register_user(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/account/settings",
|
||||||
|
request_body = UpdateUserPayoad,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Account settings successfully updated", body = UpdateUserResponse),
|
||||||
|
(status = 400, description = "Required fields are empty, Invalid email format, or Password must be at least 8 characters", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 409, description = "Username already taken or Email already in use", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error or Internal error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn update_user(
|
pub async fn update_user(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -249,6 +287,21 @@ pub async fn update_user(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/account/upload-avatar",
|
||||||
|
request_body(content = [u8], content_type = "image/*", description = "Binary payload of raw avatar image"),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Avatar successfully uploaded"),
|
||||||
|
(status = 400, description = "Wrong file format", body = ErrorResponse),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error or Internal error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn upload_avatar(
|
async fn upload_avatar(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(db): Extension<PgPool>,
|
Extension(db): Extension<PgPool>,
|
||||||
@@ -314,6 +367,18 @@ async fn upload_avatar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public route
|
// Public route
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/account/get-avatar/{uuid}",
|
||||||
|
params(
|
||||||
|
("uuid" = Uuid, Path, description = "UUID of the user whose avatar image is requested")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Avatar image served successfully"),
|
||||||
|
(status = 404, description = "Avatar not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn get_avatar(
|
async fn get_avatar(
|
||||||
Path(uuid): Path<Uuid>,
|
Path(uuid): Path<Uuid>,
|
||||||
Extension(config): Extension<Arc<AppConfig>>,
|
Extension(config): Extension<Arc<AppConfig>>,
|
||||||
|
|||||||
+16
-1
@@ -16,7 +16,7 @@ use uuid::Uuid;
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::verify_jwt_string,
|
auth::verify_jwt_string,
|
||||||
db::{room_id_from_uuid, user_id_from_uuid},
|
db::{room_id_from_uuid, user_id_from_uuid},
|
||||||
errors::APIError,
|
errors::{APIError, ErrorResponse},
|
||||||
realtime::RealTimeVoices,
|
realtime::RealTimeVoices,
|
||||||
routes::{rooms::is_member, ws::WsAuthQuery},
|
routes::{rooms::is_member, ws::WsAuthQuery},
|
||||||
};
|
};
|
||||||
@@ -25,6 +25,21 @@ pub fn routes() -> Router {
|
|||||||
Router::new().route("/ws/voice/{room_uuid}", get(voice_ws_handler))
|
Router::new().route("/ws/voice/{room_uuid}", get(voice_ws_handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/ws/voice/{room_uuid}",
|
||||||
|
params(
|
||||||
|
("room_uuid" = Uuid, Path, description = "UUID of the room voice channel to join"),
|
||||||
|
WsAuthQuery
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 101, description = "WebSocket voice channel upgrade handshake accepted"),
|
||||||
|
(status = 401, description = "Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 403, description = "You are not a member of this room", body = ErrorResponse),
|
||||||
|
(status = 404, description = "User not found or Room not found", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn voice_ws_handler(
|
async fn voice_ws_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
Path(room_uuid): Path<Uuid>,
|
Path(room_uuid): Path<Uuid>,
|
||||||
|
|||||||
+14
-2
@@ -5,9 +5,9 @@ use axum::{Extension, http::StatusCode};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::auth::{create_jwt, verify_jwt};
|
use crate::auth::{create_jwt, verify_jwt};
|
||||||
use crate::errors::APIError;
|
use crate::errors::{APIError, ErrorResponse};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, serde::Serialize, Deserialize)]
|
#[derive(sqlx::FromRow, serde::Serialize, Deserialize, utoipa::IntoParams, utoipa::ToSchema)]
|
||||||
pub struct WsAuthQuery {
|
pub struct WsAuthQuery {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,18 @@ pub fn routes() -> axum::Router {
|
|||||||
axum::Router::new().route("/ws/issue-token", get(issue_ws_token))
|
axum::Router::new().route("/ws/issue-token", get(issue_ws_token))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/ws/issue-token",
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "WebSocket authentication token successfully issued", body = WsAuthQuery),
|
||||||
|
(status = 401, description = "Missing authentication header or Invalid or expired token", body = ErrorResponse),
|
||||||
|
(status = 500, description = "Database error", body = ErrorResponse)
|
||||||
|
),
|
||||||
|
security(
|
||||||
|
("bearer_auth" = [])
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn issue_ws_token(
|
pub async fn issue_ws_token(
|
||||||
Extension(db): Extension<sqlx::PgPool>,
|
Extension(db): Extension<sqlx::PgPool>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
|||||||
Reference in New Issue
Block a user