diff --git a/Cargo.lock b/Cargo.lock index 078b65f..5bec2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,18 +14,18 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -205,17 +205,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "bson" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a88e82b9106923b5c4d6edfca9e7db958d4e98a478ec115022e81b9b38e2c8" +checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" dependencies = [ "ahash", "base64 0.13.1", @@ -359,9 +359,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.18" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "shlex", ] @@ -382,6 +382,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -603,6 +604,23 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "deadpool" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed" +dependencies = [ + "deadpool-runtime", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + [[package]] name = "deranged" version = "0.3.11" @@ -636,6 +654,56 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "diesel" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" +dependencies = [ + "bitflags 2.6.0", + "byteorder", + "chrono", + "diesel_derives", + "itoa", +] + +[[package]] +name = "diesel-async" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb799bb6f8ca6a794462125d7b8983b0c86e6c93a33a9c55934a4a5de4409d3" +dependencies = [ + "async-trait", + "deadpool", + "diesel", + "futures-util", + "scoped-futures", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "diesel_derives" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn 2.0.77", +] + [[package]] name = "diff" version = "0.1.13" @@ -662,6 +730,26 @@ dependencies = [ "const-random", ] +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling 0.20.10", + "either", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -677,7 +765,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -699,6 +787,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.1.1" @@ -855,9 +949,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "h2" @@ -920,6 +1014,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1029,7 +1129,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1054,9 +1154,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -1074,9 +1174,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1297,11 +1397,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1434,6 +1534,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.36.4" @@ -1445,9 +1555,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" @@ -1565,9 +1675,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -1576,9 +1686,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -1586,9 +1696,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", @@ -1599,15 +1709,33 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", "sha2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1646,6 +1774,35 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "postgres-protocol" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1757,9 +1914,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -1896,6 +2053,8 @@ dependencies = [ "bytes", "chrono", "config", + "diesel", + "diesel-async", "futures", "jsonwebtoken", "mime", @@ -1950,9 +2109,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -1975,13 +2134,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -2023,9 +2182,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2053,6 +2212,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scoped-futures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +dependencies = [ + "cfg-if", + "pin-utils", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2277,6 +2446,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -2560,6 +2735,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-postgres" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2 0.5.7", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -2576,7 +2777,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -2618,9 +2819,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "serde", @@ -2817,15 +3018,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -2838,9 +3039,9 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "untrusted" @@ -2932,6 +3133,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -3015,6 +3222,17 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "widestring" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 9301272..c8cdb09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,15 +25,17 @@ tower-http = { version = "0.6", features = [ "sensitive-headers", "cors", ] } -chrono = "0.4.38" +chrono = { version = "0.4", features = ["serde"] } async-trait = "0.1.81" # Investigate if wither::bson can be used instead and activate this feature. bson = { version = "2.10.0", features = ["serde_with", "chrono-0_4"] } jsonwebtoken = "9.3.0" -once_cell = "1.20.0" +once_cell = "1.19.0" bcrypt = "0.15.1" validator = { version = "0.18.1", features = ["derive"] } mime = "0.3.17" +diesel = { version = "2.1", features = ["chrono"] } +diesel-async = { version = "0.5", features = ["postgres", "deadpool"] } bytes = "1.7.2" axum-extra = { version = "0.9.3", features = ["typed-header"] } diff --git a/config/default.json b/config/default.json index 12e57db..ebfdb49 100644 --- a/config/default.json +++ b/config/default.json @@ -1,19 +1,15 @@ { "environment": "development", - "server": { "port": 8080 }, - "database": { - "uri": "mongodb://localhost:27017", + "uri": "postgresql://localhost:5432", "name": "rustapi" }, - "auth": { "secret": "secret" }, - "logger": { "level": "debug" } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..5d7f0cc --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/Users/ndelvalle/Code/ndelvalle/rustapi/migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/2024-07-14-055806_create_users/down.sql b/migrations/2024-07-14-055806_create_users/down.sql new file mode 100644 index 0000000..9951735 --- /dev/null +++ b/migrations/2024-07-14-055806_create_users/down.sql @@ -0,0 +1 @@ +DROP TABLE users diff --git a/migrations/2024-07-14-055806_create_users/up.sql b/migrations/2024-07-14-055806_create_users/up.sql new file mode 100644 index 0000000..9bf3bad --- /dev/null +++ b/migrations/2024-07-14-055806_create_users/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + locked_at TIMESTAMP +); + +SELECT diesel_manage_updated_at('users'); diff --git a/migrations/2024-07-14-134620_create_cats/down.sql b/migrations/2024-07-14-134620_create_cats/down.sql new file mode 100644 index 0000000..978669e --- /dev/null +++ b/migrations/2024-07-14-134620_create_cats/down.sql @@ -0,0 +1 @@ +DROP TABLE cats diff --git a/migrations/2024-07-14-134620_create_cats/up.sql b/migrations/2024-07-14-134620_create_cats/up.sql new file mode 100644 index 0000000..f6262a9 --- /dev/null +++ b/migrations/2024-07-14-134620_create_cats/up.sql @@ -0,0 +1,9 @@ +CREATE TABLE cats ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +SELECT diesel_manage_updated_at('cats '); diff --git a/src/database.rs b/src/database.rs index 48c5ede..6c93ebe 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,21 +1,33 @@ -use mongodb::Database; -use tokio::sync::OnceCell; -use wither::mongodb; +use diesel_async::pooled_connection::deadpool::{self, Pool}; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::AsyncPgConnection; +use once_cell::sync::OnceCell; use crate::settings::SETTINGS; -static CONNECTION: OnceCell = OnceCell::const_new(); +type PooledConnection = deadpool::Object; -pub async fn connection() -> &'static Database { - CONNECTION - .get_or_init(|| async { - let db_uri = SETTINGS.database.uri.as_str(); - let db_name = SETTINGS.database.name.as_str(); +static CONNECTION: OnceCell> = OnceCell::new(); - mongodb::Client::with_uri_str(db_uri) - .await - .expect("Failed to initialize MongoDB connection") - .database(db_name) - }) +pub async fn get_connection() -> PooledConnection { + let conn = get_connection_pool() .await + .get() + .await + .expect("Failed to get connection from the Pool"); + + conn +} + +async fn get_connection_pool() -> &'static Pool { + CONNECTION.get_or_init(|| { + let db_uri = SETTINGS.database.uri.as_str(); + let db_name = SETTINGS.database.name.as_str(); + let db_url = format!("{}/{}", db_uri, db_name); + + let config = AsyncDieselConnectionManager::::new(db_url); + Pool::builder(config) + .build() + .expect("Failed to initialize PostgreSQL connection pool") + }) } diff --git a/src/errors.rs b/src/errors.rs index 3859941..deb2f21 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,6 +2,7 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use bcrypt::BcryptError; +use diesel::result::Error as DieselError; use serde_json::json; use tokio::task::JoinError; use wither::bson; @@ -14,6 +15,9 @@ pub enum Error { #[error("{0}")] Wither(#[from] WitherError), + #[error("{0}")] + Diesel(#[from] DieselError), + #[error("{0}")] Mongo(#[from] MongoError), @@ -59,6 +63,7 @@ impl Error { (StatusCode::INTERNAL_SERVER_ERROR, 5001) } Error::Wither(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5002), + Error::Diesel(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5002), Error::Mongo(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5003), Error::SerializeMongoResponse(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5004), Error::RunSyncTask(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5005), diff --git a/src/main.rs b/src/main.rs index 5e32fb2..e5b456b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod errors; mod logger; mod models; mod routes; +mod schema; mod settings; mod utils; diff --git a/src/models/cat.rs b/src/models/cat.rs index 660f4cd..424d2ad 100644 --- a/src/models/cat.rs +++ b/src/models/cat.rs @@ -7,9 +7,9 @@ use wither::Model as WitherModel; use crate::utils::date; use crate::utils::date::Date; -use crate::utils::models::ModelExt; +// use crate::utils::models::ModelExt; -impl ModelExt for Cat {} +// impl ModelExt for Cat {} #[derive(Debug, Clone, Serialize, Deserialize, WitherModel, Validate)] #[model(index(keys = r#"doc!{ "user": 1, "created_at": 1 }"#))] diff --git a/src/models/mod.rs b/src/models/mod.rs index 0ace409..e7db1e5 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,12 +1,11 @@ pub mod cat; pub mod user; -use crate::utils::models::ModelExt; +use crate::utils::models::QueryModelExt; use crate::Error; pub async fn sync_indexes() -> Result<(), Error> { - user::User::sync_indexes().await?; - cat::Cat::sync_indexes().await?; - + // user::User::sync_indexes().await?; + // cat::Cat::sync_indexes().await?; Ok(()) } diff --git a/src/models/user.rs b/src/models/user.rs index dba8baa..28f1d0d 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,50 +1,70 @@ -use bson::serde_helpers::bson_datetime_as_rfc3339_string; -use bson::serde_helpers::serialize_object_id_as_hex_string; +use chrono::{NaiveDateTime, Utc}; +use diesel::associations::Identifiable; +use diesel::prelude::{Insertable, Queryable, Selectable}; +use diesel::query_dsl::methods::FilterDsl; +use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use tokio::task; use validator::Validate; -use wither::bson::{doc, oid::ObjectId}; -use wither::Model as WitherModel; +use crate::database; use crate::errors::Error; -use crate::utils::date; -use crate::utils::date::Date; -use crate::utils::models::ModelExt; +use crate::schema::users; -impl ModelExt for User {} - -#[derive(Debug, Clone, Serialize, Deserialize, WitherModel, Validate)] -#[model(index(keys = r#"doc!{ "email": 1 }"#, options = r#"doc!{ "unique": true }"#))] +#[derive(Debug, Clone, Serialize, Deserialize, Identifiable, Queryable, Selectable)] +#[diesel(table_name = crate::schema::users)] +#[diesel(check_for_backend(diesel::pg::Pg))] pub struct User { - #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] - pub id: Option, - #[validate(length(min = 1))] + pub id: i32, pub name: String, - #[validate(email)] pub email: String, pub password: String, - pub updated_at: Date, - pub created_at: Date, - pub locked_at: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub locked_at: Option, +} + +#[derive(Debug, Insertable, Validate)] +#[diesel(table_name = crate::schema::users)] +pub struct NewUser<'a> { + #[validate(length(min = 5))] + pub name: &'a str, + #[validate(email)] + pub email: &'a str, + pub password: &'a str, } impl User { - pub fn new(name: A, email: B, password_hash: C) -> Self - where - A: Into, - B: Into, - C: Into, - { - let now = date::now(); - Self { - id: None, - name: name.into(), - email: email.into(), - password: password_hash.into(), - updated_at: now, - created_at: now, - locked_at: None, - } + pub async fn create(name: &str, email: &str, password_hash: &str) -> User { + let now = Utc::now().naive_utc(); + let new_user = NewUser { + name, + email, + password: password_hash, + }; + + let mut conn = database::get_connection().await; + let user = diesel::insert_into(users::table) + .values(&new_user) + .get_result::(&mut conn) + .await + .expect("Error saving new post"); + + user + } + + pub async fn find_by_email(value: &str) -> Result, Error> { + use crate::schema::users::dsl::*; + use diesel::ExpressionMethods; + use diesel::OptionalExtension; + + let mut conn = database::get_connection().await; + users + .filter(email.eq(value)) + .first::(&mut conn) + .await + .optional() + .map_err(Error::Diesel) } pub fn is_password_match(&self, password: &str) -> bool { @@ -54,20 +74,17 @@ impl User { #[derive(Debug, Serialize, Deserialize)] pub struct PublicUser { - #[serde(alias = "_id", serialize_with = "serialize_object_id_as_hex_string")] - pub id: ObjectId, + pub id: i32, pub name: String, pub email: String, - #[serde(with = "bson_datetime_as_rfc3339_string")] - pub updated_at: Date, - #[serde(with = "bson_datetime_as_rfc3339_string")] - pub created_at: Date, + pub updated_at: NaiveDateTime, + pub created_at: NaiveDateTime, } impl From for PublicUser { fn from(user: User) -> Self { Self { - id: user.id.unwrap(), + id: user.id, name: user.name.clone(), email: user.email.clone(), updated_at: user.updated_at, diff --git a/src/routes/cat.rs b/src/routes/cat.rs index c9b6cf1..707877a 100644 --- a/src/routes/cat.rs +++ b/src/routes/cat.rs @@ -13,7 +13,6 @@ use crate::errors::Error; use crate::models::cat::{Cat, PublicCat}; use crate::utils::custom_response::CustomResponseResult as Response; use crate::utils::custom_response::{CustomResponse, CustomResponseBuilder, ResponsePagination}; -use crate::utils::models::ModelExt; use crate::utils::pagination::Pagination; use crate::utils::to_object_id::to_object_id; use crate::utils::token::TokenUser; diff --git a/src/routes/user.rs b/src/routes/user.rs index 4b74bb3..c6066a8 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -9,7 +9,6 @@ use crate::models::user; use crate::models::user::{PublicUser, User}; use crate::settings::SETTINGS; use crate::utils::custom_response::{CustomResponse, CustomResponseBuilder}; -use crate::utils::models::ModelExt; use crate::utils::token; pub fn create_route() -> Router { @@ -20,8 +19,8 @@ pub fn create_route() -> Router { async fn create_user(Json(body): Json) -> Result, Error> { let password_hash = user::hash_password(body.password).await?; - let user = User::new(body.name, body.email, password_hash); - let user = User::create(user).await?; + // let user = User::new(body.name, body.email, password_hash); + let user = User::create(&body.name, &body.email, &password_hash).await; let res = PublicUser::from(user); let res = CustomResponseBuilder::new() @@ -48,7 +47,7 @@ async fn authenticate_user( return Err(Error::bad_request()); } - let user = User::find_one(doc! { "email": email }, None).await?; + let user = User::find_by_email(email).await?; let user = match user { Some(user) => user, diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..47e8fe1 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,34 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + cats (id) { + id -> Int4, + user_id -> Int4, + #[max_length = 255] + name -> Varchar, + created_at -> Timestamp, + updated_at -> Timestamp, + } +} + +diesel::table! { + users (id) { + id -> Int4, + #[max_length = 255] + name -> Varchar, + #[max_length = 255] + email -> Varchar, + #[max_length = 255] + password -> Varchar, + created_at -> Timestamp, + updated_at -> Timestamp, + locked_at -> Nullable, + } +} + +diesel::joinable!(cats -> users (user_id)); + +diesel::allow_tables_to_appear_in_same_query!( + cats, + users, +); diff --git a/src/utils/models.rs b/src/utils/models.rs index 557d45d..175ccf0 100644 --- a/src/utils/models.rs +++ b/src/utils/models.rs @@ -1,202 +1,58 @@ -#![allow(dead_code)] - use async_trait::async_trait; -use futures::stream::TryStreamExt; -use serde::{de::DeserializeOwned, ser::Serialize}; -use validator::Validate; -use wither::bson::doc; -use wither::bson::from_bson; -use wither::bson::Bson; -use wither::bson::Document; -use wither::bson::{self, oid::ObjectId}; -use wither::mongodb::options::FindOneAndUpdateOptions; -use wither::mongodb::options::FindOneOptions; -use wither::mongodb::options::FindOptions; -use wither::mongodb::options::ReturnDocument; -use wither::mongodb::options::UpdateOptions; -use wither::mongodb::results::DeleteResult; -use wither::mongodb::results::UpdateResult; -use wither::Model as WitherModel; -use wither::ModelCursor; + +use diesel::pg::Pg; + +use diesel::associations::HasTable; +use diesel::prelude::Queryable; +use diesel::prelude::Table; +use diesel_async::RunQueryDsl; use crate::database; use crate::errors::Error; -// This is the Model trait. All models that have a MongoDB collection should -// implement this and therefore inherit theses methods. #[async_trait] -pub trait ModelExt +pub trait QueryModelExt where - Self: WitherModel + Validate, + Self: Queryable + HasTable + Send + Sync, + T: Table + Send + Sync, { - async fn create(mut model: Self) -> Result { - let connection = database::connection().await; - model.validate().map_err(|_error| Error::bad_request())?; - model.save(connection, None).await.map_err(Error::Wither)?; - - Ok(model) - } - - async fn find_by_id(id: &ObjectId) -> Result, Error> { - let connection = database::connection().await; - ::find_one(connection, doc! { "_id": id }, None) - .await - .map_err(Error::Wither) - } - - async fn find_one(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find_one(connection, query, options) - .await - .map_err(Error::Wither) - } - - async fn find(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find(connection, query, options) - .await - .map_err(Error::Wither)? - .try_collect::>() - .await - .map_err(Error::Wither) - } - - async fn find_and_count(query: Document, options: O) -> Result<(Vec, u64), Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - - let count = Self::collection(connection) - .count_documents(query.clone(), None) - .await - .map_err(Error::Mongo)?; - - let items = ::find(connection, query, options.into()) - .await - .map_err(Error::Wither)? - .try_collect::>() - .await - .map_err(Error::Wither)?; - - Ok((items, count)) - } - - async fn cursor(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find(connection, query, options) - .await - .map_err(Error::Wither) - } - - async fn find_one_and_update(query: Document, update: Document) -> Result, Error> { - let connection = database::connection().await; - let options = FindOneAndUpdateOptions::builder() - .return_document(ReturnDocument::After) - .build(); - - ::find_one_and_update(connection, query, update, options) - .await - .map_err(Error::Wither) - } - - async fn update_one( - query: Document, - update: Document, - options: O, - ) -> Result - where - O: Into> + Send, - { - let connection = database::connection().await; - Self::collection(connection) - .update_one(query, update, options) - .await - .map_err(Error::Mongo) - } - - async fn update_many( - query: Document, - update: Document, - options: O, - ) -> Result - where - O: Into> + Send, - { - let connection = database::connection().await; - Self::collection(connection) - .update_many(query, update, options) - .await - .map_err(Error::Mongo) - } - - async fn delete_many(query: Document) -> Result { - let connection = database::connection().await; - ::delete_many(connection, query, None) - .await - .map_err(Error::Wither) - } - - async fn delete_one(query: Document) -> Result { - let connection = database::connection().await; - Self::collection(connection) - .delete_one(query, None) - .await - .map_err(Error::Mongo) - } - - async fn count(query: Document) -> Result { - let connection = database::connection().await; - Self::collection(connection) - .count_documents(query, None) - .await - .map_err(Error::Mongo) - } - - async fn exists(query: Document) -> Result { - let connection = database::connection().await; - let count = Self::collection(connection) - .count_documents(query, None) - .await - .map_err(Error::Mongo)?; - - Ok(count > 0) - } - - async fn aggregate(pipeline: Vec) -> Result, Error> - where - A: Serialize + DeserializeOwned, - { - let connection = database::connection().await; - - let documents = Self::collection(connection) - .aggregate(pipeline, None) - .await - .map_err(Error::Mongo)? - .try_collect::>() - .await - .map_err(Error::Mongo)?; - - let documents = documents - .into_iter() - .map(|document| from_bson::(Bson::Document(document))) - .collect::, bson::de::Error>>() - .map_err(Error::SerializeMongoResponse)?; - - Ok(documents) + async fn find_by_id(id: i32) -> Result, Error> { + let conn = database::get_connection().await; + use diesel::prelude::*; + Self::table().find(id).first::(conn).optional() } +} - async fn sync_indexes() -> Result<(), Error> { - let connection = database::connection().await; - Self::sync(connection).await.map_err(Error::Wither) - } +impl QueryModelExt for U +where + U: Queryable + HasTable
+ Send + Sync, + T: Table + Send + Sync, +{ } + +// #[async_trait] +// pub trait QueryModelExt: Sized { +// type Table: Table; +// +// async fn find_by_id(id: i32) -> Result, Error> +// where +// Self: Queryable, +// Self::Table: FindDsl, +// { +// let mut conn = database::get_connection().await; +// Self::Table::table() +// .find(id) +// .first::(&mut conn) +// .await +// .optional() +// .map_err(Error::Diesel) +// } +// } +// +// impl QueryModelExt for T +// where +// T: HasTable + Queryable + Send + Sync, +// T::Table: Table + FindDsl + Send + Sync, +// { +// type Table = T::Table; +// }