From afb70856cd20b7965a231a2728ecf50f7d05878a Mon Sep 17 00:00:00 2001 From: Daniel Lundin Date: Wed, 8 Jan 2025 11:58:35 +0100 Subject: [PATCH] feat: Add user resource w/database as storage --- .cargo/audit.toml | 8 + Cargo.lock | 686 +++++++++++++++++- agent/Cargo.toml | 1 + api.json | 62 +- ...dc8fdfc05f489328e8376513124dfb42996e3.json | 46 ++ controller/Cargo.toml | 4 + controller/build.rs | 5 + .../migrations/20250108132540_users.sql | 7 + controller/src/api.rs | 2 + controller/src/bin/patagia-controller.rs | 23 +- controller/src/context.rs | 16 +- controller/src/lib.rs | 1 + controller/src/user/api.rs | 110 +++ controller/src/user/mod.rs | 14 + flake.nix | 4 + justfile | 30 +- 16 files changed, 989 insertions(+), 30 deletions(-) create mode 100644 .cargo/audit.toml create mode 100644 controller/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json create mode 100644 controller/build.rs create mode 100644 controller/migrations/20250108132540_users.sql create mode 100644 controller/src/user/api.rs create mode 100644 controller/src/user/mod.rs diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..352d558 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,8 @@ +[advisories] +ignore = [ + # Advisory about a vulnerability in rsa, which we don't use, but comes via sqlx due + # to a bug in cargo. For context, see: + # https://github.com/launchbadge/sqlx/issues/2911 + # and https://github.com/rust-lang/cargo/issues/10801 + "RUSTSEC-2023-0071" +] diff --git a/Cargo.lock b/Cargo.lock index 0c8d346..9250d52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -218,7 +227,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -227,11 +236,20 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -302,7 +320,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -352,6 +370,21 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -387,6 +420,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -405,6 +453,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -427,6 +484,17 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe7ed1d93f4553003e20b629abe9085e1e81b1429520f897f8f8860bc6dfc21" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -443,7 +511,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -478,6 +548,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dropshot" version = "0.15.1" @@ -554,6 +630,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" @@ -580,6 +659,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -596,12 +697,29 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -668,6 +786,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -790,6 +919,20 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] [[package]] name = "heck" @@ -803,6 +946,39 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -1221,6 +1397,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1228,6 +1407,12 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libredox" version = "0.1.3" @@ -1238,6 +1423,16 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1287,6 +1482,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1363,12 +1568,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1376,6 +1618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1560,6 +1803,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1580,7 +1829,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1602,6 +1851,7 @@ dependencies = [ "serde", "tokio", "tracing", + "uuid", ] [[package]] @@ -1617,10 +1867,21 @@ dependencies = [ "serde", "slog", "slog-async", + "sqlx", "tokio", "trace-request", "tracing", "tracing-slog", + "uuid", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -1661,6 +1922,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -2011,6 +2293,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2314,6 +2616,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2338,6 +2651,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -2407,6 +2730,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2423,6 +2749,217 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +dependencies = [ + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink", + "indexmap 2.7.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls 0.23.20", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.9", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.9", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.9", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "time", + "tracing", + "url", + "uuid", +] [[package]] name = "stable_deref_trait" @@ -2430,6 +2967,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -2861,6 +3409,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3000,12 +3549,33 @@ dependencies = [ "typify-impl", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3099,6 +3669,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.99" @@ -3208,6 +3784,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3237,7 +3823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3246,7 +3832,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3257,7 +3843,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3266,7 +3852,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3276,7 +3862,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -3285,7 +3880,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3294,7 +3889,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3303,28 +3913,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3337,24 +3965,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 4edcf82..38023c0 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -14,6 +14,7 @@ schemars.workspace = true serde.workspace = true tokio.workspace = true tracing.workspace = true +uuid.workspace = true [package.metadata.cargo-machete] ignored = ["reqwest", "serde"] diff --git a/api.json b/api.json index 9c88ea6..b37a4f9 100644 --- a/api.json +++ b/api.json @@ -5,6 +5,44 @@ "version": "1.0.0" }, "paths": { + "/users/{userId}": { + "get": { + "tags": [ + "user" + ], + "summary": "Fetch user info.", + "operationId": "get_user_by_id", + "parameters": [ + { + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/version": { "get": { "summary": "Fetch version info.", @@ -51,6 +89,23 @@ "request_id" ] }, + "User": { + "description": "User", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, "VersionInfo": { "description": "Version and build information", "type": "object", @@ -80,5 +135,10 @@ } } } - } + }, + "tags": [ + { + "name": "user" + } + ] } diff --git a/controller/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json b/controller/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json new file mode 100644 index 0000000..043a176 --- /dev/null +++ b/controller/.sqlx/query-843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM users WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "time_deleted", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "time_created", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "time_modified", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + true, + false, + false + ] + }, + "hash": "843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3" +} diff --git a/controller/Cargo.toml b/controller/Cargo.toml index baa4054..7157501 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -15,10 +15,14 @@ schemars.workspace = true serde.workspace = true slog-async.workspace = true slog.workspace = true +sqlx = { version = "0.8.3", default-features = false, features = [ + "macros", "migrate", "postgres", "runtime-tokio", "tls-rustls", "time", "uuid" + ] } tokio.workspace = true trace-request = { path = "../trace-request" } tracing-slog.workspace = true tracing.workspace = true +uuid.workspace = true [package.metadata.cargo-machete] ignored = ["http"] diff --git a/controller/build.rs b/controller/build.rs new file mode 100644 index 0000000..d506869 --- /dev/null +++ b/controller/build.rs @@ -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"); +} diff --git a/controller/migrations/20250108132540_users.sql b/controller/migrations/20250108132540_users.sql new file mode 100644 index 0000000..f6e7e8d --- /dev/null +++ b/controller/migrations/20250108132540_users.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS patagia.public.Users( + id UUID PRIMARY KEY, + name VARCHAR(63) NOT NULL, + time_deleted TIMESTAMP WITH TIME ZONE, -- non-NULL if deleted + time_created TIMESTAMP WITH TIME ZONE NOT NULL, + time_modified TIMESTAMP WITH TIME ZONE NOT NULL +); diff --git a/controller/src/api.rs b/controller/src/api.rs index 1906567..5be86ce 100644 --- a/controller/src/api.rs +++ b/controller/src/api.rs @@ -4,12 +4,14 @@ use dropshot::ApiDescription; use std::sync::Arc; use crate::context::ControllerContext; +use crate::user; use crate::version; type ControllerApiDescription = ApiDescription>; pub fn api() -> Result { let mut api = ControllerApiDescription::new(); + user::register_api(&mut api)?; api.register(version::version)?; Ok(api) } diff --git a/controller/src/bin/patagia-controller.rs b/controller/src/bin/patagia-controller.rs index 845cfd6..a73f4a7 100644 --- a/controller/src/bin/patagia-controller.rs +++ b/controller/src/bin/patagia-controller.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use clap::Parser; use dropshot::{ConfigDropshot, ServerBuilder}; - use slog::Drain; +use sqlx::postgres::PgPool; use tracing_slog::TracingSlogDrain; use std::net::SocketAddr; @@ -36,6 +36,13 @@ struct Cli { env = "LISTEN_ADDRESS" )] listen_address: String, + + #[arg( + long = "database-url", + default_value = "postgresql://localhost/patagia", + env = "DATABASE_URL" + )] + database_url: Option, } #[tokio::main] @@ -57,7 +64,19 @@ async fn main() -> Result<()> { slog::Logger::root(async_drain, slog::o!()) }; - let ctx = ControllerContext::new(); + let database_url = args.database_url.unwrap(); + + tracing::info!( + database_url, + listen_address = args.listen_address, + "Starting server" + ); + + let pg = PgPool::connect(&database_url).await?; + + sqlx::migrate!().run(&pg).await?; + + let ctx = ControllerContext::new(pg); let api = api::api()?; ServerBuilder::new(api, Arc::new(ctx), logger) .config(config) diff --git a/controller/src/context.rs b/controller/src/context.rs index d994d44..b99d559 100644 --- a/controller/src/context.rs +++ b/controller/src/context.rs @@ -1,13 +1,11 @@ -pub struct ControllerContext {} +use sqlx::postgres::PgPool; + +pub struct ControllerContext { + pub pg_pool: PgPool, +} impl ControllerContext { - pub fn new() -> ControllerContext { - ControllerContext {} - } -} - -impl Default for ControllerContext { - fn default() -> Self { - Self::new() + pub fn new(pg_pool: PgPool) -> ControllerContext { + ControllerContext { pg_pool } } } diff --git a/controller/src/lib.rs b/controller/src/lib.rs index 0caaf72..2d12df1 100644 --- a/controller/src/lib.rs +++ b/controller/src/lib.rs @@ -1,4 +1,5 @@ pub mod api; pub mod context; +mod user; mod version; diff --git a/controller/src/user/api.rs b/controller/src/user/api.rs new file mode 100644 index 0000000..861db83 --- /dev/null +++ b/controller/src/user/api.rs @@ -0,0 +1,110 @@ +use dropshot::{ + endpoint, EmptyScanParams, HttpError, HttpResponseOk, PaginationParams, Path, Query, + RequestContext, ResultsPage, WhichPage, +}; +use dropshot::{ApiDescription, ApiDescriptionRegisterError}; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use trace_request::trace_request; +use uuid::Uuid; + +use std::sync::Arc; + +use super::User; +use crate::context::ControllerContext; + +#[derive(Deserialize, JsonSchema, Serialize)] +#[serde(rename_all = "camelCase")] +struct UsersPathParams { + user_id: Uuid, +} + +#[derive(Deserialize, JsonSchema, Serialize)] +#[serde(rename_all = "camelCase")] +struct UserPage { + user_id: Uuid, +} + +pub fn register_api( + api: &mut ApiDescription>, +) -> Result<(), ApiDescriptionRegisterError> { + api.register(get_user_by_id)?; + api.register(list_users) +} + +/// Fetch user info. +#[endpoint { + method = GET, + path = "/users/{userId}", + tags = [ "user" ], +}] +#[trace_request] +async fn get_user_by_id( + rqctx: RequestContext>, + params: Path, +) -> Result, HttpError> { + let id = params.into_inner().user_id; + tracing::debug!(id = id.to_string(), "Getting user by id"); + + let pg = rqctx.context().pg_pool.to_owned(); + + let rec = sqlx::query!(r#"SELECT * FROM users WHERE id = $1"#, id) + .fetch_one(&pg) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => { + HttpError::for_not_found(None, format!("User not found by id: {:?}", id)) + } + err => HttpError::for_internal_error(format!("Error: {}", err)), + })?; + + let user = User { + id: rec.id, + name: rec.name, + }; + + Ok(HttpResponseOk(user)) +} + +/// List users +#[endpoint { + method = GET, + path = "/users", + tags = [ "user" ], +}] +#[trace_request] +async fn list_users( + rqctx: RequestContext>, + query: Query>, +) -> Result>, HttpError> { + let pag_params = query.into_inner(); + let limit = rqctx.page_limit(&pag_params)?.get() as usize; + let pg = rqctx.context().pg_pool.to_owned(); + + let q = match &pag_params.page { + WhichPage::First(..) => None, + WhichPage::Next(UserPage { user_id: last_seen }) => Some(last_seen), + }; + + let users = sqlx::query!( + r#"SELECT * FROM users WHERE id > coalesce($1, '00000000-0000-0000-0000-000000000000'::UUID) ORDER BY id LIMIT $2"#, + q, + limit as i64 + ) + .fetch_all(&pg) + .await + .map_err(|e| HttpError::for_internal_error(format!("Error: {}", e)))? + .into_iter() + .map(|rec| User { + id: rec.id, + name: rec.name, + }) + .collect(); + + Ok(HttpResponseOk(ResultsPage::new( + users, + &EmptyScanParams {}, + |u: &User, _| UserPage { user_id: u.id }, + )?)) +} diff --git a/controller/src/user/mod.rs b/controller/src/user/mod.rs new file mode 100644 index 0000000..46397e9 --- /dev/null +++ b/controller/src/user/mod.rs @@ -0,0 +1,14 @@ +use schemars::JsonSchema; +use serde::Serialize; +use uuid::Uuid; + +mod api; + +pub use self::api::register_api; + +/// User +#[derive(Serialize, JsonSchema)] +struct User { + id: Uuid, + name: String, +} diff --git a/flake.nix b/flake.nix index 4b22c31..7f35a94 100644 --- a/flake.nix +++ b/flake.nix @@ -49,6 +49,7 @@ root = ./.; fileset = pkgs.lib.fileset.unions [ ./api.json + ./controller/.sqlx (craneLib.fileset.commonCargoSources ./.) ]; }; @@ -116,6 +117,7 @@ formatter = (treefmt-nix.lib.evalModule pkgs { projectRootFile = "flake.nix"; + programs = { nixfmt.enable = true; nixfmt.package = pkgs.nixfmt-rfc-style; @@ -141,6 +143,8 @@ just nixfmt-rfc-style rust-dev-toolchain + sqls + sqlx-cli watchexec ] ++ commonArgs.buildInputs; diff --git a/justfile b/justfile index f7b69d7..9dabd7b 100644 --- a/justfile +++ b/justfile @@ -5,11 +5,13 @@ default: @just --choose # Run controller +[group('controller')] run-controller $RUST_LOG="debug,h2=info,hyper_util=info,tower=info": cargo run --package patagia-controller -- --log-stderr # Run controller local development -dev-controller: +[group('controller')] +dev-controller: dev-controller-db-migrate watchexec --clear --restart --stop-signal INT --debounce 300ms -- just run-controller # Run agent @@ -48,6 +50,10 @@ machete: open-api: cargo xtask open-api +# Update OpenAPI spec +gen-open-api: + cargo xtask open-api > api.json + # Run all tests check: check-nix @@ -56,6 +62,7 @@ check-nix: nix flake check # Run PostgreSQL for development and testing +[group('controller')] dev-postgres: mkdir -p "${XDG_RUNTIME_DIR}/patagia-postgres" podman volume exists patagia-postgres || podman volume create patagia-postgres @@ -69,12 +76,33 @@ dev-postgres: --volume patagia-postgres:/var/lib/postgresql/data \ --volume "${XDG_RUNTIME_DIR}/patagia-postgres:/var/run/postgresql" \ docker.io/postgres:17 + sleep 0.3 # Clean up PostgreSQL data +[group('controller')] dev-postgres-clean: podman rm -f patagia-postgres || true podman volume rm patagia-postgres || true # Connect to PostgreSQL with psql +[group('controller')] dev-postgres-psql: podman exec -it patagia-postgres psql -U patagia + +[group('controller')] +[working-directory: 'controller'] +dev-controller-db-migrate: dev-postgres + cargo sqlx migrate run + +[group('controller')] +[working-directory: 'controller'] +dev-controller-db-reset: + cargo sqlx db reset -y + +[group('controller')] +[working-directory: 'controller'] +gen-controller-sqlx-prepare: + cargo sqlx prepare + +gen: gen-open-api gen-controller-sqlx-prepare fmt +