From f13097591cd53e49f8a0d933b9d141d5b849d133 Mon Sep 17 00:00:00 2001 From: mathieu Date: Fri, 15 May 2026 21:46:16 +0200 Subject: [PATCH] feat(db): add SQLx migrations and AppState with connection pool - Add sqlx 0.8 (AnyPool, runtime-tokio, sqlite, postgres, migrate) - Create 6 migration files for both SQLite and PostgreSQL backends - Add server/db.rs: create_pool and run_migrations helpers - Add server/state.rs: AppState with LeptosOptions + AnyPool - Run migrations at server startup before accepting requests - Fix Port model: remove host_id (ports are now a global catalog) - Add HostPort join struct for the host_ports many-to-many table Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 950 +++++++++++++++++- Cargo.toml | 4 + migrations/postgres/0001_create_networks.sql | 6 + migrations/postgres/0002_create_hosts.sql | 7 + migrations/postgres/0003_create_ports.sql | 5 + .../postgres/0004_create_applications.sql | 5 + .../postgres/0005_create_host_ports.sql | 6 + .../0006_create_application_ports.sql | 6 + migrations/sqlite/0001_create_networks.sql | 6 + migrations/sqlite/0002_create_hosts.sql | 9 + migrations/sqlite/0003_create_ports.sql | 7 + .../sqlite/0004_create_applications.sql | 6 + migrations/sqlite/0005_create_host_ports.sql | 7 + .../sqlite/0006_create_application_ports.sql | 8 + src/main.rs | 43 +- src/models.rs | 24 +- src/server/db.rs | 68 ++ src/server/mod.rs | 6 + src/server/state.rs | 42 + 19 files changed, 1192 insertions(+), 23 deletions(-) create mode 100644 migrations/postgres/0001_create_networks.sql create mode 100644 migrations/postgres/0002_create_hosts.sql create mode 100644 migrations/postgres/0003_create_ports.sql create mode 100644 migrations/postgres/0004_create_applications.sql create mode 100644 migrations/postgres/0005_create_host_ports.sql create mode 100644 migrations/postgres/0006_create_application_ports.sql create mode 100644 migrations/sqlite/0001_create_networks.sql create mode 100644 migrations/sqlite/0002_create_hosts.sql create mode 100644 migrations/sqlite/0003_create_ports.sql create mode 100644 migrations/sqlite/0004_create_applications.sql create mode 100644 migrations/sqlite/0005_create_host_ports.sql create mode 100644 migrations/sqlite/0006_create_application_ports.sql create mode 100644 src/server/db.rs create mode 100644 src/server/state.rs diff --git a/Cargo.lock b/Cargo.lock index 607836f..722686b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "any_spawner" version = "0.2.0" @@ -51,6 +57,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" @@ -87,6 +102,12 @@ dependencies = [ "syn", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.7.9" @@ -149,11 +170,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "bumpalo" @@ -161,6 +200,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -173,6 +218,16 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -228,6 +283,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_format" version = "0.2.36" @@ -273,12 +334,55 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -293,6 +397,17 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "derive-where" version = "1.6.1" @@ -304,6 +419,18 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -338,6 +465,9 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "either_of" @@ -371,7 +501,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] @@ -395,6 +536,23 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[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 = "foldhash" version = "0.1.5" @@ -452,6 +610,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.32" @@ -498,6 +667,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -574,6 +753,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -583,12 +764,54 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -892,6 +1115,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "leb128fmt" @@ -1122,6 +1348,35 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.5", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linear-map" version = "1.2.0" @@ -1187,6 +1442,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.8.0" @@ -1217,7 +1482,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1249,7 +1514,53 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", ] [[package]] @@ -1298,7 +1609,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -1315,6 +1626,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1347,6 +1667,39 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "potential_utf" version = "0.1.5" @@ -1555,6 +1908,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.3" @@ -1584,6 +1946,40 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rstml" version = "0.12.1" @@ -1612,6 +2008,7 @@ dependencies = [ "leptos_meta", "leptos_router", "serde", + "sqlx", "thiserror 1.0.69", "tokio", "tower-http 0.5.2", @@ -1626,6 +2023,40 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1814,6 +2245,28 @@ dependencies = [ "syn", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1823,6 +2276,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -1833,6 +2292,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.12" @@ -1853,6 +2322,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -1861,7 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1869,6 +2341,209 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "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.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +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.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", +] [[package]] name = "stable_deref_trait" @@ -1876,6 +2551,23 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -2018,6 +2710,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.52.3" @@ -2032,7 +2739,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2046,6 +2753,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -2250,18 +2968,45 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + [[package]] name = "unicase" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[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.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.13.2" @@ -2274,6 +3019,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2315,6 +3066,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2355,6 +3112,12 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[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.121" @@ -2467,13 +3230,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2482,6 +3273,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2491,6 +3300,127 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "1.0.3" @@ -2676,6 +3606,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index c76f7c9..6f3240b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ ssr = [ "dep:tracing-subscriber", "dep:dotenvy", "dep:ipnetwork", + "dep:sqlx", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", @@ -68,6 +69,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = tr dotenvy = { version = "0.15", optional = true } # Parsing et calcul de plages d'adresses IP (CIDR) — ex: 192.168.1.0/24 ipnetwork = { version = "0.20", optional = true } +# Database access: connection pools, queries, migrations — SQLite + PostgreSQL +# "any" = runtime-dispatched driver (same code works with both backends) +sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "sqlite", "postgres", "migrate", "any"], optional = true } # --- Dépendances client uniquement (activées par la feature "hydrate") --- diff --git a/migrations/postgres/0001_create_networks.sql b/migrations/postgres/0001_create_networks.sql new file mode 100644 index 0000000..72bf691 --- /dev/null +++ b/migrations/postgres/0001_create_networks.sql @@ -0,0 +1,6 @@ +-- networks: IP address ranges managed by the IPAM. +-- BIGSERIAL: auto-incrementing 64-bit integer (PostgreSQL's equivalent of AUTOINCREMENT). +CREATE TABLE IF NOT EXISTS networks ( + id BIGSERIAL PRIMARY KEY, + cidr TEXT NOT NULL UNIQUE +); diff --git a/migrations/postgres/0002_create_hosts.sql b/migrations/postgres/0002_create_hosts.sql new file mode 100644 index 0000000..938c168 --- /dev/null +++ b/migrations/postgres/0002_create_hosts.sql @@ -0,0 +1,7 @@ +-- hosts: physical or virtual machines belonging to a network. +CREATE TABLE IF NOT EXISTS hosts ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + ip TEXT NOT NULL, + network_id BIGINT NOT NULL REFERENCES networks(id) ON DELETE CASCADE +); diff --git a/migrations/postgres/0003_create_ports.sql b/migrations/postgres/0003_create_ports.sql new file mode 100644 index 0000000..9fb1c7c --- /dev/null +++ b/migrations/postgres/0003_create_ports.sql @@ -0,0 +1,5 @@ +-- ports: global catalog of TCP/UDP port numbers and their known protocol. +CREATE TABLE IF NOT EXISTS ports ( + number INTEGER PRIMARY KEY, + description TEXT +); diff --git a/migrations/postgres/0004_create_applications.sql b/migrations/postgres/0004_create_applications.sql new file mode 100644 index 0000000..a6bea8a --- /dev/null +++ b/migrations/postgres/0004_create_applications.sql @@ -0,0 +1,5 @@ +-- applications: software stacks that use one or more ports. +CREATE TABLE IF NOT EXISTS applications ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL +); diff --git a/migrations/postgres/0005_create_host_ports.sql b/migrations/postgres/0005_create_host_ports.sql new file mode 100644 index 0000000..8bc2606 --- /dev/null +++ b/migrations/postgres/0005_create_host_ports.sql @@ -0,0 +1,6 @@ +-- host_ports: which ports are open on which host (many-to-many). +CREATE TABLE IF NOT EXISTS host_ports ( + host_id BIGINT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE, + port_number INTEGER NOT NULL REFERENCES ports(number) ON DELETE CASCADE, + PRIMARY KEY (host_id, port_number) +); diff --git a/migrations/postgres/0006_create_application_ports.sql b/migrations/postgres/0006_create_application_ports.sql new file mode 100644 index 0000000..3ec29df --- /dev/null +++ b/migrations/postgres/0006_create_application_ports.sql @@ -0,0 +1,6 @@ +-- application_ports: which ports an application typically uses (many-to-many). +CREATE TABLE IF NOT EXISTS application_ports ( + application_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE, + port_number INTEGER NOT NULL, + PRIMARY KEY (application_id, port_number) +); diff --git a/migrations/sqlite/0001_create_networks.sql b/migrations/sqlite/0001_create_networks.sql new file mode 100644 index 0000000..86ef47a --- /dev/null +++ b/migrations/sqlite/0001_create_networks.sql @@ -0,0 +1,6 @@ +-- networks: IP address ranges managed by the IPAM. +-- Each network has a unique CIDR block (e.g. "192.168.1.0/24"). +CREATE TABLE IF NOT EXISTS networks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cidr TEXT NOT NULL UNIQUE +); diff --git a/migrations/sqlite/0002_create_hosts.sql b/migrations/sqlite/0002_create_hosts.sql new file mode 100644 index 0000000..a3cfa61 --- /dev/null +++ b/migrations/sqlite/0002_create_hosts.sql @@ -0,0 +1,9 @@ +-- hosts: physical or virtual machines belonging to a network. +-- The ip field must fall within the CIDR of the parent network +-- (enforced in application code, not at the DB level). +CREATE TABLE IF NOT EXISTS hosts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + ip TEXT NOT NULL, + network_id INTEGER NOT NULL REFERENCES networks(id) ON DELETE CASCADE +); diff --git a/migrations/sqlite/0003_create_ports.sql b/migrations/sqlite/0003_create_ports.sql new file mode 100644 index 0000000..0054b3f --- /dev/null +++ b/migrations/sqlite/0003_create_ports.sql @@ -0,0 +1,7 @@ +-- ports: global catalog of TCP/UDP port numbers and their known protocol. +-- Ports are not tied to a specific host here — host_ports handles that link. +-- Port numbers range from 0 to 65535 (fits in INTEGER). +CREATE TABLE IF NOT EXISTS ports ( + number INTEGER PRIMARY KEY, + description TEXT +); diff --git a/migrations/sqlite/0004_create_applications.sql b/migrations/sqlite/0004_create_applications.sql new file mode 100644 index 0000000..6a19d6d --- /dev/null +++ b/migrations/sqlite/0004_create_applications.sql @@ -0,0 +1,6 @@ +-- applications: software stacks that use one or more ports. +-- Examples: "Nginx", "PostgreSQL", "Prometheus". +CREATE TABLE IF NOT EXISTS applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL +); diff --git a/migrations/sqlite/0005_create_host_ports.sql b/migrations/sqlite/0005_create_host_ports.sql new file mode 100644 index 0000000..db5547b --- /dev/null +++ b/migrations/sqlite/0005_create_host_ports.sql @@ -0,0 +1,7 @@ +-- host_ports: which ports are open on which host (many-to-many). +-- Composite primary key prevents duplicate (host, port) pairs. +CREATE TABLE IF NOT EXISTS host_ports ( + host_id INTEGER NOT NULL REFERENCES hosts(id) ON DELETE CASCADE, + port_number INTEGER NOT NULL REFERENCES ports(number) ON DELETE CASCADE, + PRIMARY KEY (host_id, port_number) +); diff --git a/migrations/sqlite/0006_create_application_ports.sql b/migrations/sqlite/0006_create_application_ports.sql new file mode 100644 index 0000000..0844a29 --- /dev/null +++ b/migrations/sqlite/0006_create_application_ports.sql @@ -0,0 +1,8 @@ +-- application_ports: which ports an application typically uses (many-to-many). +-- port_number is not a strict FK to ports to allow registering an application +-- before its port entry exists in the catalog. +CREATE TABLE IF NOT EXISTS application_ports ( + application_id INTEGER NOT NULL REFERENCES applications(id) ON DELETE CASCADE, + port_number INTEGER NOT NULL, + PRIMARY KEY (application_id, port_number) +); diff --git a/src/main.rs b/src/main.rs index 2780f0b..4938bb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,12 @@ async fn main() { use leptos_axum::{generate_route_list, LeptosRoutes}; use rust_ipam::{ app::{App, Shell}, - server::{config::AppConfig, routes::not_found_handler}, + server::{ + config::AppConfig, + db::{create_pool, run_migrations}, + routes::not_found_handler, + state::AppState, + }, }; use tower_http::services::ServeDir; @@ -44,6 +49,18 @@ async fn main() { tracing::info!("Database: {} ({})", app_config.backend, app_config.database_url); + // Connect to the database and apply any pending migrations. + // The server cannot serve data without a working database, so we abort on error. + let pool = create_pool(&app_config) + .await + .expect("Failed to connect to database"); + + run_migrations(&pool, &app_config.backend) + .await + .expect("Database migration failed"); + + tracing::info!("Database ready."); + // `Some("Cargo.toml")` tells Leptos to read the [package.metadata.leptos] // section from Cargo.toml (file paths, output names, server address...). let conf = get_configuration(Some("Cargo.toml")) @@ -51,27 +68,37 @@ async fn main() { let leptos_options = conf.leptos_options; let addr = leptos_options.site_addr; + // Combine Leptos options and the database pool into a single shared state. + // `AppState` implements `FromRef for LeptosOptions` so Leptos + // can still extract just what it needs from the full state. + let state = AppState { leptos_options: leptos_options.clone(), db: pool }; + // Walk all `` components inside `App` to build the list of URLs // that Leptos SSR must handle. let routes = generate_route_list(App); // Build the Axum router using the builder pattern (method chaining). - let app = Router::new() + // + // `Router::::new()` explicitly tells Rust the state type is `AppState`. + // Without this annotation, type inference would default to `LeptosOptions` + // (inferred from `leptos_routes`) and then reject `.with_state(state: AppState)`. + let app = Router::::new() // Serve static files compiled by trunk (WASM, JS...). // Trunk places them in target/site/pkg/ as configured in [package.metadata.leptos]. .nest_service("/pkg", ServeDir::new("target/site/pkg")) // Mount all Leptos routes into Axum. // For each URL, Axum renders Shell() to HTML and sends it to the browser. - // Shell() contains App(), which provides the page content. - .leptos_routes(&leptos_options, routes, { - // Clone options so the closure can capture them. + // `leptos_routes` receives `&state` (the full AppState). + // It extracts `LeptosOptions` via `FromRef` implemented in state.rs. + .leptos_routes(&state, routes, { + // Clone options before moving into the closure. // `move` transfers ownership of `leptos_options` into the closure. - let leptos_options = leptos_options.clone(); + let leptos_options = state.leptos_options.clone(); move || view! { } }) .fallback(not_found_handler) - // Share Leptos options with all handlers via Axum's state system. - .with_state(leptos_options); + // Share AppState (Leptos options + DB pool) with all handlers. + .with_state(state); let listener = tokio::net::TcpListener::bind(&addr) .await diff --git a/src/models.rs b/src/models.rs index 29e3ae8..62edad3 100644 --- a/src/models.rs +++ b/src/models.rs @@ -57,24 +57,32 @@ pub struct Host { // ─── Port ───────────────────────────────────────────────────────────────────── -/// A network port open on a host, with its likely protocol description. +/// A network port entry in the global port catalog. /// +/// Ports are defined once here; host_ports and application_ports link them +/// to hosts and applications through separate join tables. /// Well-known ports (0–1023) have standardized protocol assignments. -/// A port can be associated with multiple applications (non-strict relation). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Port { - /// TCP/UDP port number. - /// `u16` is an unsigned 16-bit integer → range 0 to 65535, - /// which exactly matches the valid range for network ports. + /// TCP/UDP port number (0–65535). + /// `u16` is an unsigned 16-bit integer — the exact range for port numbers. pub number: u16, - /// Description of the likely protocol on this port. - /// `Option`: may be absent (None) when the protocol is unknown. + /// Description of the protocol typically running on this port. + /// `Option`: absent (None) when the protocol is unknown. /// Examples: Some("SSH"), Some("HTTPS"), None pub description: Option, +} - /// The host on which this port is open. +// ─── HostPort ───────────────────────────────────────────────────────────────── + +/// Join record representing a port open on a specific host. +/// +/// Maps to the `host_ports` table (many-to-many between hosts and ports). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HostPort { pub host_id: i64, + pub port_number: u16, } impl Port { diff --git a/src/server/db.rs b/src/server/db.rs new file mode 100644 index 0000000..e124b7e --- /dev/null +++ b/src/server/db.rs @@ -0,0 +1,68 @@ +// server/db.rs — Database connection pool and migrations +// +// This module provides two functions called once at server startup: +// - `create_pool` : opens a connection pool to the database +// - `run_migrations` : applies all pending SQL migrations +// +// `AnyPool` lets the same Rust code target both SQLite (dev) and +// PostgreSQL (production) — only DATABASE_URL changes. + +use sqlx::AnyPool; +use thiserror::Error; + +use crate::server::config::{AppConfig, DatabaseBackend}; + +// ─── Errors ─────────────────────────────────────────────────────────────────── + +#[derive(Debug, Error)] +pub enum DbError { + #[error("Database connection failed: {0}")] + Connection(#[from] sqlx::Error), + + #[error("Migration failed: {0}")] + Migration(#[from] sqlx::migrate::MigrateError), +} + +// ─── Pool creation ──────────────────────────────────────────────────────────── + +/// Opens a connection pool to the database specified in `config.database_url`. +/// +/// A pool maintains multiple open connections so concurrent requests +/// do not block each other waiting for a single connection. +/// +/// `install_default_drivers()` must be called before `AnyPool::connect` +/// to register both the SQLite and PostgreSQL drivers in the `Any` registry. +pub async fn create_pool(config: &AppConfig) -> Result { + // Register SQLite and PostgreSQL drivers so `AnyPool` can dispatch + // to the correct one based on the URL scheme (sqlite:// vs postgres://). + sqlx::any::install_default_drivers(); + + let pool = AnyPool::connect(&config.database_url).await?; + Ok(pool) +} + +// ─── Migrations ─────────────────────────────────────────────────────────────── + +/// Applies all pending migrations from the directory matching the active backend. +/// +/// Two separate directories handle SQL syntax differences: +/// - `migrations/sqlite/` : uses `INTEGER PRIMARY KEY AUTOINCREMENT` +/// - `migrations/postgres/` : uses `BIGSERIAL PRIMARY KEY` +/// +/// SQLx tracks applied migrations in a `_sqlx_migrations` table, so running +/// this function on an already-migrated database is always safe (idempotent). +/// +/// `sqlx::migrate!("path")` is a compile-time macro: it embeds all `.sql` +/// files from the given path directly into the binary. The path is relative +/// to the project root (where Cargo.toml lives). +pub async fn run_migrations(pool: &AnyPool, backend: &DatabaseBackend) -> Result<(), DbError> { + match backend { + DatabaseBackend::Sqlite => { + sqlx::migrate!("migrations/sqlite").run(pool).await?; + } + DatabaseBackend::Postgres => { + sqlx::migrate!("migrations/postgres").run(pool).await?; + } + } + Ok(()) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index b7e95a0..55d1055 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -11,5 +11,11 @@ pub mod routes; #[cfg(feature = "ssr")] pub mod config; +#[cfg(feature = "ssr")] +pub mod db; + +#[cfg(feature = "ssr")] +pub mod state; + #[cfg(feature = "ssr")] pub mod validation; diff --git a/src/server/state.rs b/src/server/state.rs new file mode 100644 index 0000000..578893b --- /dev/null +++ b/src/server/state.rs @@ -0,0 +1,42 @@ +// server/state.rs — Shared Axum application state +// +// Axum uses a typed state system: any handler can extract a specific type +// from the shared state using the `State` extractor. +// +// We store two pieces of state: +// - `leptos_options` : required by Leptos SSR routes +// - `db` : database pool shared across all requests + +use axum::extract::FromRef; +use leptos::config::LeptosOptions; +use sqlx::AnyPool; + +// ─── AppState ───────────────────────────────────────────────────────────────── + +/// Shared state available to every Axum handler and Leptos server function. +/// +/// `#[derive(Clone)]` is required by Axum: each request receives its own clone. +/// Both fields are cheap to clone — they hold reference-counted pointers +/// under the hood, so cloning just increments an atomic counter. +#[derive(Clone)] +pub struct AppState { + /// Leptos configuration (output paths, site address, reload port…). + pub leptos_options: LeptosOptions, + + /// Shared connection pool to the database. + /// Using a pool (not a single connection) allows concurrent requests + /// to run their queries in parallel without contention. + pub db: AnyPool, +} + +// ─── FromRef implementations ────────────────────────────────────────────────── + +/// Tells Axum how to extract just `LeptosOptions` from the full `AppState`. +/// +/// `.leptos_routes()` in main.rs requires this impl because it stores +/// only `LeptosOptions` internally, not the full application state. +impl FromRef for LeptosOptions { + fn from_ref(state: &AppState) -> Self { + state.leptos_options.clone() + } +}