Started on member migration

This commit is contained in:
xeovalyte 2025-01-31 13:36:09 +01:00
parent 169e89cefb
commit 9cf9e5752f
30 changed files with 1122 additions and 58 deletions

View File

@ -1,6 +1,10 @@
{ ... }: { pkgs, ... }:
{ {
packages = with pkgs; [
openssl
];
languages.rust.enable = true; languages.rust.enable = true;
languages.javascript = { languages.javascript = {

View File

@ -1 +1,2 @@
DATABASE_URL="postgres://wrbapp:password@localhost/wrbapp" DATABASE_URL="postgres://wrbapp:password@localhost/wrbapp"
API_TOKEN="SuperSecretToken"

1
server/.gitignore vendored
View File

@ -1 +1,2 @@
target target
.env

507
server/Cargo.lock generated
View File

@ -17,12 +17,36 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 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]] [[package]]
name = "atoi" name = "atoi"
version = "2.0.0" version = "2.0.0"
@ -93,6 +117,28 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-extra"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum-macros" name = "axum-macros"
version = "0.5.0" version = "0.5.0"
@ -119,6 +165,12 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@ -149,6 +201,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@ -161,12 +219,35 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cc"
version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@ -182,6 +263,12 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.16"
@ -231,6 +318,62 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "csv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.9" version = "0.7.9"
@ -446,7 +589,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -475,6 +630,30 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "headers"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
dependencies = [
"base64 0.21.7",
"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]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -595,6 +774,29 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"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]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
@ -713,6 +915,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.0.3" version = "1.0.3"
@ -744,12 +952,31 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -853,7 +1080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -879,7 +1106,7 @@ dependencies = [
"num-integer", "num-integer",
"num-iter", "num-iter",
"num-traits", "num-traits",
"rand", "rand 0.8.5",
"smallvec", "smallvec",
"zeroize", "zeroize",
] ]
@ -1024,7 +1251,29 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [ dependencies = [
"zerocopy", "zerocopy 0.7.35",
]
[[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",
] ]
[[package]] [[package]]
@ -1052,8 +1301,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.0",
"zerocopy 0.8.14",
] ]
[[package]] [[package]]
@ -1063,7 +1323,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "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.0",
] ]
[[package]] [[package]]
@ -1072,7 +1342,17 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
]
[[package]]
name = "rand_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"getrandom 0.3.1",
"zerocopy 0.8.14",
] ]
[[package]] [[package]]
@ -1084,6 +1364,35 @@ dependencies = [
"bitflags", "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",
"regex-syntax",
]
[[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",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.7" version = "0.9.7"
@ -1097,7 +1406,7 @@ dependencies = [
"num-traits", "num-traits",
"pkcs1", "pkcs1",
"pkcs8", "pkcs8",
"rand_core", "rand_core 0.6.4",
"signature", "signature",
"spki", "spki",
"subtle", "subtle",
@ -1163,9 +1472,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.135" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1226,6 +1535,12 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@ -1233,7 +1548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [ dependencies = [
"digest", "digest",
"rand_core", "rand_core 0.6.4",
] ]
[[package]] [[package]]
@ -1375,7 +1690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64 0.22.1",
"bitflags", "bitflags",
"byteorder", "byteorder",
"bytes", "bytes",
@ -1397,7 +1712,7 @@ dependencies = [
"memchr", "memchr",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"rand", "rand 0.8.5",
"rsa", "rsa",
"serde", "serde",
"sha1", "sha1",
@ -1417,7 +1732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64 0.22.1",
"bitflags", "bitflags",
"byteorder", "byteorder",
"crc", "crc",
@ -1435,7 +1750,7 @@ dependencies = [
"md-5", "md-5",
"memchr", "memchr",
"once_cell", "once_cell",
"rand", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -1487,6 +1802,12 @@ dependencies = [
"unicode-properties", "unicode-properties",
] ]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -1529,7 +1850,7 @@ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom 0.2.15",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@ -1770,6 +2091,42 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
[[package]]
name = "validator"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77"
dependencies = [
"darling",
"once_cell",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"
@ -1794,12 +2151,79 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasite" name = "wasite"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 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",
"wasm-bindgen-shared",
]
[[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",
"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]] [[package]]
name = "whoami" name = "whoami"
version = "1.5.2" version = "1.5.2"
@ -1832,6 +2256,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@ -1980,17 +2413,35 @@ 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 = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "wrbapp_server" name = "wrbapp_server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"axum-extra",
"chrono",
"csv",
"dotenvy", "dotenvy",
"itertools",
"rand 0.9.0",
"serde", "serde",
"serde_json",
"sqlx", "sqlx",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"uuid",
"validator",
] ]
[[package]] [[package]]
@ -2036,7 +2487,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"zerocopy-derive", "zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
dependencies = [
"zerocopy-derive 0.8.14",
] ]
[[package]] [[package]]
@ -2050,6 +2510,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "zerocopy-derive"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.5" version = "0.1.5"

View File

@ -4,13 +4,26 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
axum = { version = "0.8", features = [ "macros" ] } # Primary crates
axum = { version = "0.8", features = [ "macros", "json" ] }
axum-extra = { version = "0.10.0", features = [ "typed-header" ] }
tokio = { version = "1.43", features = [ "rt-multi-thread", "macros" ] } tokio = { version = "1.43", features = [ "rt-multi-thread", "macros" ] }
sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres" ] } sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres" ] }
# Secondary crates
csv = { version = "1.3" }
serde = "1.0" serde = "1.0"
dotenvy = "0.15.7" dotenvy = "0.15.7"
validator = { version = "0.19.0", features = [ "derive" ] }
# Tracing
# Tertiary crates
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
chrono = "0.4.39"
uuid = "1.12.0"
serde_json = "1.0.137"
rand = "0.9"
thiserror = { version = "2.0" }
itertools = "0.14"

5
server/build.rs Normal file
View File

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View File

@ -1,28 +0,0 @@
CREATE TABLE user (
id bigint NOT NULL PRIMARY KEY,
email text NOT NULL UNIQUE,
password text NOT NULL,
admin boolean NOT NULL
);
CREATE TABLE member (
id varchar(7) NOT NULL PRIMARY KEY,
call_sign text NOT NULL,
name text NOT NULL,
registration_token text NOT NULL UNIQUE,
diploma text,
hours text[] NOT NULL,
groups text[] NOT NULL
);
CREATE TABLE session (
id bigint NOT NULL PRIMARY KEY,
user_id bigint NOT NULL,
token text NOT NULL UNIQUE,
expires timestamp NOT NULL
);
ALTER TABLE session ADD CONSTRAINT session_user_id_fk FOREIGN KEY (user_id) REFERENCES user (id);

View File

@ -0,0 +1,10 @@
create table "members" (
id varchar(7) primary key,
first_name text not null,
full_name text not null,
registration_token text unique not null,
diploma text,
hours text[] not null,
groups text[] not null
);

48
server/src/auth.rs Normal file
View File

@ -0,0 +1,48 @@
use std::collections::HashSet;
use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
use axum_extra::{
headers::{authorization::Bearer, Authorization},
typed_header::TypedHeaderRejectionReason,
TypedHeader,
};
use bearer::verify_bearer;
pub use error::AuthError;
mod bearer;
mod error;
#[derive(Debug)]
pub struct Permissions<'a>(pub HashSet<&'a str>);
// Middleware for getting permissions
impl<S> FromRequestParts<S> for Permissions<'_>
where
S: Send + Sync,
{
type Rejection = crate::Error;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
// First check if the request has a beaerer token to authenticate
match parts.extract::<TypedHeader<Authorization<Bearer>>>().await {
Ok(bearer) => {
verify_bearer(bearer.token().to_string()).map_err(|_| AuthError::InvalidToken)?;
let permissions = Permissions {
0: HashSet::from(["root"]),
};
return Ok(permissions);
}
Err(err) => match err.reason() {
TypedHeaderRejectionReason::Missing => (),
TypedHeaderRejectionReason::Error(_err) => {
return Err(AuthError::InvalidToken.into())
}
_ => return Err(AuthError::Unexpected.into()),
},
};
Err(AuthError::Unexpected.into())
}
}

View File

@ -0,0 +1,8 @@
pub fn verify_bearer(token: String) -> Result<(), ()> {
let env_api_token = dotenvy::var("API_TOKEN").map_err(|_| ())?;
match env_api_token == token {
true => Ok(()),
false => Err(()),
}
}

20
server/src/auth/error.rs Normal file
View File

@ -0,0 +1,20 @@
use std::fmt::Display;
#[derive(Debug)]
pub enum AuthError {
NoPermssions,
InvalidToken,
Unexpected,
}
impl Display for AuthError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoPermssions => write!(f, "{}", "No permissions"),
Self::InvalidToken => write!(f, "{}", "Invalid token"),
Self::Unexpected => write!(f, "{}", "Unexpected error"),
}
}
}
impl std::error::Error for AuthError {}

View File

@ -1,3 +1,5 @@
mod postgres; mod postgres;
pub use postgres::apply_migrations; pub use postgres::apply_migrations;
pub use postgres::connect; pub use postgres::connect;
pub mod model;

View File

@ -0,0 +1,5 @@
pub mod member;
pub mod session;
pub mod user;
pub use member::Member;

View File

@ -0,0 +1,54 @@
use rand::distr::{Alphanumeric, SampleString};
use sqlx::{Postgres, QueryBuilder};
use validator::Validate;
#[derive(Debug, Validate)]
pub struct Member {
#[validate(length(equal = 7))]
pub id: String,
pub first_name: String,
pub full_name: String,
pub registration_token: Option<String>,
pub diploma: Option<String>,
pub hours: Vec<String>,
pub groups: Vec<String>,
}
impl Member {
pub async fn insert_multiple(
transaction: &mut sqlx::Transaction<'_, Postgres>,
members: Vec<Self>,
) -> Result<(), sqlx::Error> {
let mut query_builder = QueryBuilder::new(
"INSERT INTO members(id, first_name, full_name, registration_token, diploma, hours, groups) "
);
query_builder.push_values(members.into_iter(), |mut b, member| {
let registration_token = Alphanumeric.sample_string(&mut rand::rng(), 16);
b.push_bind(member.id);
b.push_bind(member.first_name);
b.push_bind(member.full_name);
b.push_bind(registration_token);
b.push_bind(member.diploma);
b.push_bind(member.hours);
b.push_bind(member.groups);
});
let query = query_builder.build();
query.execute(&mut **transaction).await?;
Ok(())
}
pub async fn update_multiple(
transaction: &mut sqlx::Transaction<'_, Postgres>,
members: Vec<Self>,
) -> Result<(), sqlx::Error> {
for member in members {
sqlx::query!("UPDATE ONLY members SET first_name = $1, full_name = $2, diploma = $3, hours = $4, groups = $5 WHERE id = $6", member.first_name, member.full_name, member.diploma, &member.hours, &member.groups, member.id).execute(&mut **transaction).await?;
}
Ok(())
}
}

View File

@ -0,0 +1,6 @@
struct Session {
id: u32,
user_id: u32,
token: String,
expires: chrono::NaiveDateTime,
}

View File

@ -0,0 +1,8 @@
#[derive(validator::Validate)]
struct User {
pub id: uuid::Uuid,
#[validate(email)]
pub email: String,
pub password: String,
pub admin: bool,
}

View File

@ -1 +1,18 @@
use routes::member::migrate::MigrationStore;
use sqlx::{Pool, Postgres};
use std::sync::Arc;
use tokio::sync::Mutex;
pub mod auth;
pub mod database; pub mod database;
pub mod model;
pub mod routes;
pub mod util;
pub use util::error::Error;
#[derive(Clone)]
pub struct AppState {
pub pool: Pool<Postgres>,
pub migration_store: Arc<Mutex<MigrationStore>>,
}

View File

@ -1,9 +1,13 @@
use axum::{http::StatusCode, routing::get, Router}; use std::sync::Arc;
use tokio::net::TcpListener;
use axum::Router;
use tokio::{net::TcpListener, sync::Mutex};
use tracing::Level; use tracing::Level;
use tracing_subscriber::FmtSubscriber; use tracing_subscriber::FmtSubscriber;
use wrbapp_server::database; use wrbapp_server::routes::member::migrate::MigrationStore;
use wrbapp_server::routes::routes;
use wrbapp_server::{database, AppState};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -22,12 +26,19 @@ async fn main() {
.await .await
.expect("Database migrations failed"); .expect("Database migrations failed");
database::connect() let pool = database::connect()
.await .await
.expect("Database connection failed"); .expect("Database connection failed");
let migration_store = Arc::new(Mutex::new(MigrationStore::default()));
let app_state = AppState {
pool,
migration_store,
};
// Serve app // Serve app
let app = Router::new().route("/", get(hello_world)); let app = Router::new().merge(routes()).with_state(app_state);
let listener = TcpListener::bind("127.0.0.1:3000") let listener = TcpListener::bind("127.0.0.1:3000")
.await .await
@ -39,7 +50,3 @@ async fn main() {
.await .await
.expect("Error while serving axum application"); .expect("Error while serving axum application");
} }
async fn hello_world() -> Result<String, (StatusCode, String)> {
Ok("Hello world".to_string())
}

6
server/src/model.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod member;
pub mod session;
pub mod user;
pub use member::Member;
pub use user::User;

View File

@ -0,0 +1,46 @@
#[derive(Clone, serde::Serialize)]
pub struct Name {
pub first: String,
pub full: String,
}
#[derive(Clone, serde::Serialize)]
pub struct Member {
pub id: String,
pub name: Name,
pub registration_token: Option<String>,
pub diploma: Option<String>,
pub hours: Vec<String>,
pub groups: Vec<String>,
}
use crate::database::model::Member as DbMember;
impl From<DbMember> for Member {
fn from(value: DbMember) -> Self {
Member {
id: value.id,
name: Name {
first: value.first_name,
full: value.full_name,
},
registration_token: value.registration_token,
diploma: value.diploma,
hours: value.hours,
groups: value.groups,
}
}
}
impl From<Member> for DbMember {
fn from(value: Member) -> Self {
DbMember {
id: value.id,
first_name: value.name.first,
full_name: value.name.full,
registration_token: None,
diploma: value.diploma,
hours: value.hours,
groups: value.groups,
}
}
}

View File

5
server/src/model/user.rs Normal file
View File

@ -0,0 +1,5 @@
pub struct User {
pub id: uuid::Uuid,
pub email: String,
pub admin: bool,
}

30
server/src/routes.rs Normal file
View File

@ -0,0 +1,30 @@
use axum::{
extract::State,
http::StatusCode,
routing::{get, post},
Router,
};
use member::migrate::{migrate_confirm, migrate_request};
use crate::{auth::Permissions, AppState};
pub mod auth;
pub mod member;
pub mod user;
pub fn routes() -> Router<AppState> {
Router::new()
.route("/", get(root))
// .route("/member/:id", get())
.route("/members/migrate_request", post(migrate_request))
.route("/members/migrate_confirm", post(migrate_confirm))
}
async fn root(
State(state): State<AppState>,
permissions: Permissions<'_>,
) -> Result<String, (StatusCode, String)> {
tracing::info!("{:?}", permissions);
Ok("Hello world".to_string())
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
pub mod migrate;

View File

@ -0,0 +1,260 @@
use std::collections::HashMap;
use axum::{extract::State, Json};
use itertools::Itertools;
use sqlx::PgPool;
use crate::{
auth::{AuthError, Permissions},
database::model::Member as DbMember,
model::{member::Name, Member},
util::convert_vec,
AppState,
};
pub async fn migrate_request<'a>(
State(state): State<AppState>,
permissions: Permissions<'a>,
body: String,
) -> Result<Json<MigrationResponse>, crate::Error> {
if !permissions.0.contains("root") {
return Err(AuthError::NoPermssions.into());
}
// Convert the input CSV to a vector of members
let members_new: Vec<Member> = Row::from_csv_multiple(&body)?
.into_iter()
.map(|m| m.into())
.collect();
// TODO: Write function to get members from database
let members_old: Vec<Member> = Vec::new();
let members_diff = generate_diff(members_new, members_old);
let count = state
.migration_store
.lock()
.await
.insert(members_diff.clone());
Ok(Json(MigrationResponse::from((count, members_diff))))
}
pub async fn migrate_confirm<'a>(
State(state): State<AppState>,
permissions: Permissions<'a>,
body: String,
) -> Result<(), crate::Error> {
if !permissions.0.contains("root") {
return Err(AuthError::NoPermssions.into());
}
// TODO: Implement better error naming
let count = match body.trim().parse::<u32>() {
Ok(c) => c,
Err(_) => return Err(crate::Error::NotFound),
};
let mut store = state.migration_store.lock().await;
let members_diff = match store.remove(&count) {
Some(m) => m,
None => return Err(crate::Error::NotFound),
};
migrate_transaction(&state.pool, members_diff).await?;
Ok(())
}
async fn migrate_transaction(pool: &PgPool, members_diff: MembersDiff) -> Result<(), sqlx::Error> {
let mut transaction = pool.begin().await?;
// DbMember::insert_multiple(&mut transaction, convert_vec(members_diff.insert)).await?;
DbMember::update_multiple(&mut transaction, convert_vec(members_diff.update)).await?;
transaction.commit().await?;
Ok(())
}
// Create a row for the csv file
#[derive(Debug, serde::Deserialize, Clone)]
struct Row {
#[serde(rename = "Relatiecode")]
id: String,
#[serde(rename = "Roepnaam")]
first_name: String,
// #[serde(rename = "Tussenvoegsel(s)")]
// middle_name: String,
// #[serde(rename = "Achternaam")]
// last_name: String,
#[serde(rename = "E-mail")]
email: String,
#[serde(rename = "Verenigingssporten")]
hours: String,
#[serde(rename = "Diploma dropdown 1")]
diploma: Option<String>,
}
#[derive(Clone)]
pub struct MembersDiff {
insert: Vec<Member>,
update: Vec<Member>,
remove: Vec<Member>,
}
#[derive(serde::Serialize)]
pub struct MigrationResponse {
count: u32,
insert: Vec<(String, Name)>,
update: Vec<(String, Name)>,
remove: Vec<(String, Name)>,
}
pub struct MigrationStore {
pub store: HashMap<u32, MembersDiff>,
pub count: u32,
}
impl Default for MigrationStore {
fn default() -> Self {
Self {
count: 0,
store: HashMap::new(),
}
}
}
impl Row {
fn from_csv_multiple(input: &str) -> Result<Vec<Self>, csv::Error> {
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.from_reader(input.as_bytes());
let members: Result<Vec<Row>, csv::Error> = rdr.deserialize().collect();
members
}
fn hours_parsed(&self) -> Vec<String> {
let mut hours: Vec<String> = Vec::new();
let group_parts: Vec<&str> = self.hours.split(", ").collect();
for group in group_parts {
let hour_parts: Vec<&str> = group.split(" - ").collect();
for part in hour_parts {
if &*part != "Groep" {
hours.push(part.to_string());
}
}
}
hours.into_iter().unique().collect()
}
}
impl Into<Name> for Row {
fn into(self) -> Name {
Name {
first: self.first_name,
full: "Temporarely full name".to_string(),
}
}
}
impl Into<Member> for Row {
fn into(self) -> Member {
let name: Name = self.clone().into();
Member {
id: self.id.clone(),
name,
registration_token: None,
diploma: self.diploma.clone(),
hours: self.hours_parsed(),
groups: Vec::new(),
}
}
}
impl From<(u32, MembersDiff)> for MigrationResponse {
fn from(value: (u32, MembersDiff)) -> Self {
let members_insert: Vec<(String, Name)> =
value.1.insert.into_iter().map(|m| (m.id, m.name)).collect();
let members_update: Vec<(String, Name)> =
value.1.update.into_iter().map(|m| (m.id, m.name)).collect();
let members_remove: Vec<(String, Name)> =
value.1.remove.into_iter().map(|m| (m.id, m.name)).collect();
Self {
count: value.0,
insert: members_insert,
update: members_update,
remove: members_remove,
}
}
}
impl MigrationStore {
fn insert(&mut self, members_diff: MembersDiff) -> u32 {
let count = self.count + 1;
self.store.insert(count, members_diff);
self.count = count;
count
}
fn get(&self, id: &u32) -> Option<&MembersDiff> {
self.store.get(id)
}
fn remove(&mut self, id: &u32) -> Option<MembersDiff> {
self.store.remove(id)
}
}
fn generate_diff(members_new: Vec<Member>, members_old: Vec<Member>) -> MembersDiff {
let members_old_map: HashMap<String, Member> = members_old
.iter()
.map(|m| (m.id.clone(), m.clone()))
.collect();
let members_new_map: HashMap<String, Member> = members_new
.iter()
.map(|m| (m.id.clone(), m.clone()))
.collect();
let mut members_insert: Vec<Member> = Vec::new();
let mut members_update: Vec<Member> = Vec::new();
let mut members_remove: Vec<Member> = Vec::new();
for old_member in members_old {
if let Some(new_member) = members_new_map.get(&old_member.id) {
members_update.push(Member {
id: old_member.id,
name: new_member.name.clone(),
registration_token: old_member.registration_token,
diploma: new_member.diploma.clone(),
hours: new_member.hours.clone(),
groups: old_member.groups,
})
} else {
members_remove.push(old_member);
}
}
for new_member in members_new {
if !members_old_map.contains_key(&new_member.id) {
members_insert.push(new_member);
}
}
MembersDiff {
insert: members_insert,
update: members_update,
remove: members_remove,
}
}

View File

@ -0,0 +1 @@

4
server/src/util.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod error;
mod helpers;
pub use helpers::convert_vec;

52
server/src/util/error.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::auth::AuthError;
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("CSV error: {0}")]
Csv(#[from] csv::Error),
#[error("Auth error: {0}")]
Auth(#[from] crate::auth::AuthError),
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Resource not found")]
NotFound,
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
tracing::error!("Error... {:?}", self);
let (status, error_message) = match self {
Error::Auth(AuthError::NoPermssions) => {
(StatusCode::UNAUTHORIZED, String::from("No permissions"))
}
Error::Auth(AuthError::InvalidToken) => {
(StatusCode::BAD_REQUEST, String::from("Invalid token"))
}
Error::Auth(AuthError::Unexpected) => (
StatusCode::INTERNAL_SERVER_ERROR,
String::from("Unexpected error occured"),
),
Error::Csv(err) => (StatusCode::BAD_REQUEST, err.to_string()),
Error::NotFound => (
StatusCode::BAD_REQUEST,
String::from("Could not find resource"),
),
Error::Database(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
};
let body = Json(serde_json::json!({
"error": error_message
}));
(status, body).into_response()
}
}

View File

@ -0,0 +1,6 @@
pub fn convert_vec<T, U>(vec: Vec<T>) -> Vec<U>
where
U: From<T>,
{
vec.into_iter().map(U::from).collect()
}