From 0e4b219f977dc9f4db15c11b15fd316c6d0b8d76 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Wed, 29 Jan 2025 15:13:08 +0200 Subject: [PATCH 01/13] Database User & Session repository --- 3/coursework/src/.gitignore | 3 + 3/coursework/src/Cargo.lock | 5354 +++++++++++++++++ 3/coursework/src/Cargo.toml | 3 + 3/coursework/src/README.md | 19 + 3/coursework/src/database/Cargo.toml | 20 + 3/coursework/src/database/compose.yaml | 11 + 3/coursework/src/database/init/init.sql | 177 + 3/coursework/src/database/src/atomic.rs | 40 + 3/coursework/src/database/src/connect.rs | 44 + 3/coursework/src/database/src/lib.rs | 16 + 3/coursework/src/database/src/repository.rs | 2 + .../src/database/src/repository/session.rs | 51 + .../src/database/src/repository/user.rs | 164 + 13 files changed, 5904 insertions(+) create mode 100644 3/coursework/src/.gitignore create mode 100644 3/coursework/src/Cargo.lock create mode 100644 3/coursework/src/Cargo.toml create mode 100644 3/coursework/src/README.md create mode 100644 3/coursework/src/database/Cargo.toml create mode 100644 3/coursework/src/database/compose.yaml create mode 100644 3/coursework/src/database/init/init.sql create mode 100644 3/coursework/src/database/src/atomic.rs create mode 100644 3/coursework/src/database/src/connect.rs create mode 100644 3/coursework/src/database/src/lib.rs create mode 100644 3/coursework/src/database/src/repository.rs create mode 100644 3/coursework/src/database/src/repository/session.rs create mode 100644 3/coursework/src/database/src/repository/user.rs diff --git a/3/coursework/src/.gitignore b/3/coursework/src/.gitignore new file mode 100644 index 0000000..edb52da --- /dev/null +++ b/3/coursework/src/.gitignore @@ -0,0 +1,3 @@ +/target/ +/**/scrapyard/ +/database/data/ diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock new file mode 100644 index 0000000..22d0b7d --- /dev/null +++ b/3/coursework/src/Cargo.lock @@ -0,0 +1,5354 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.8.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "authentication" +version = "0.1.0" +dependencies = [ + "argon2", + "database", + "garde", + "thiserror 2.0.11", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +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 = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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 = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.8.0", + "log", + "polling", + "rustix", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.6", +] + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" +dependencies = [ + "thiserror 1.0.69", + "x11rb", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "cosmic-text" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" +dependencies = [ + "bitflags 2.8.0", + "fontdb", + "log", + "rangemap", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "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.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.8.0", + "libloading 0.8.6", + "winapi", +] + +[[package]] +name = "dark-light" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" +dependencies = [ + "dconf_rs", + "detect-desktop-environment", + "dirs", + "objc", + "rust-ini", + "web-sys", + "winreg", + "zbus", +] + +[[package]] +name = "database" +version = "0.1.0" +dependencies = [ + "chrono", + "derive_more", + "garde", + "sqlx", + "thiserror 2.0.11", +] + +[[package]] +name = "dconf_rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" + +[[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 = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "detect-desktop-environment" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" + +[[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 = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.6", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys", + "rustix", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[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 = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[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 = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "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 = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[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 0.12.3", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "garde" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a989bd2fd12136080f7825ff410d9239ce84a2a639487fc9d924ee42e2fb84f" +dependencies = [ + "compact_str", + "garde_derive", + "once_cell", + "regex", + "smallvec", + "url", +] + +[[package]] +name = "garde_derive" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7f0545bbbba0a37d4d445890fa5759814e0716f02417b39f6fab292193df68" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", +] + +[[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 = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.8.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.8.0", + "gpu-descriptor-types", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +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 = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.8.0", + "com", + "libc", + "libloading 0.8.6", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +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 = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[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 = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" +dependencies = [ + "iced_core", + "iced_futures", + "iced_renderer", + "iced_widget", + "iced_winit", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_core" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0013a238275494641bf8f1732a23a808196540dc67b22ff97099c044ae4c8a1c" +dependencies = [ + "bitflags 2.8.0", + "bytes", + "dark-light", + "glam", + "log", + "num-traits", + "once_cell", + "palette", + "rustc-hash 2.1.0", + "smol_str", + "thiserror 1.0.69", + "web-time", +] + +[[package]] +name = "iced_futures" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c04a6745ba2e80f32cf01e034fd00d853aa4f4cd8b91888099cb7aaee0d5d7c" +dependencies = [ + "futures", + "iced_core", + "log", + "rustc-hash 2.1.0", + "wasm-bindgen-futures", + "wasm-timer", +] + +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c3bb56f1820ca252bc1d0994ece33d233a55657c0c263ea7cb16895adbde82" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.0", + "wgpu", +] + +[[package]] +name = "iced_graphics" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba25a18cfa6d5cc160aca7e1b34f73ccdff21680fa8702168c09739767b6c66f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "log", + "once_cell", + "raw-window-handle", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_renderer" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73558208059f9e622df2bf434e044ee2f838ce75201a023cf0ca3e1244f46c2a" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_runtime" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348b5b2c61c934d88ca3b0ed1ed913291e923d086a66fa288ce9669da9ef62b5" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625d368284fcc43b0b36b176f76eff1abebe7959dd58bd8ce6897d641962a50" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_graphics", + "kurbo", + "log", + "rustc-hash 2.1.0", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "futures", + "glam", + "guillotiere", + "iced_glyphon", + "iced_graphics", + "log", + "once_cell", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81429e1b950b0e4bca65be4c4278fea6678ea782030a411778f26fa9f8983e1d" +dependencies = [ + "iced_renderer", + "iced_runtime", + "num-traits", + "once_cell", + "ouroboros", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44cd4e1c594b6334f409282937bf972ba14d31fedf03c23aa595d982a2fda28" +dependencies = [ + "iced_futures", + "iced_graphics", + "iced_runtime", + "log", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "window_clipboard", + "winit", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.6", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.8.0", + "libc", + "redox_syscall 0.5.8", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + +[[package]] +name = "macros" +version = "0.1.0" + +[[package]] +name = "main" +version = "0.1.0" +dependencies = [ + "iced", + "macros", + "strum", + "widget", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.8.0", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set", + "bitflags 2.8.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.8.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + +[[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-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]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.8.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.8.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.1", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[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.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.8", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[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 = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +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 = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.8.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "core-graphics 0.24.0", + "drm", + "fastrand", + "foreign-types", + "js-sys", + "log", + "memmap2", + "objc2", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.8", + "rustix", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.8.0", +] + +[[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", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.11", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[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 2.0.96", +] + +[[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 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.96", + "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 2.8.0", + "byteorder", + "bytes", + "chrono", + "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.11", + "tracing", + "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 2.8.0", + "byteorder", + "chrono", + "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.11", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[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 = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.96", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg_fmt" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" + +[[package]] +name = "swash" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading 0.8.6", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +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.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[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 = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +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.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.8.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.8.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "js-sys", + "log", + "naga", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.8.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.8.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.6", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.8.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall 0.5.8", + "wasite", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "widget" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window_clipboard" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d692d46038c433f9daee7ad8757e002a4248c20b0a3fbc991d99521d3bcb6d" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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 = "winit" +version = "0.30.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" +dependencies = [ + "ahash 0.8.11", + "android-activity", + "atomic-waker", + "bitflags 2.8.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.6", + "once_cell", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.8.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml new file mode 100644 index 0000000..78b5bb8 --- /dev/null +++ b/3/coursework/src/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["app/*", "libs/*", "database"] diff --git a/3/coursework/src/README.md b/3/coursework/src/README.md new file mode 100644 index 0000000..d393cad --- /dev/null +++ b/3/coursework/src/README.md @@ -0,0 +1,19 @@ +# Stuff that helped: + +* Architecture: + - [How to apply hexagonal architecture to Rust](https://www.barrage.net/blog/technology/how-to-apply-hexagonal-architecture-to-rust) + - [Implementing onion architecture using Rust](https://mathias-vandaele.dev/implementing-onion-architecture-using-rust) +* Design: + - [Rust Data Modelling Without Classes](https://www.youtube.com/watch?v=z-0-bbc80JM) + - ["Making Impossible States Impossible" by Richard Feldman](https://www.youtube.com/watch?v=IcgmSRJHu_8) + - [Pretty State Machine Patterns in Rust](https://hoverbear.org/blog/rust-state-machine-pattern/) +* How to Iced: + - [Building a simple text editor with iced, a cross-platform GUI library for Rust](https://www.youtube.com/watch?v=gcBJ7cPSALo) + - [Unofficial Iced Guide](https://jl710.github.io/iced-guide/) + - [icebreaker](https://github.com/hecrj/icebreaker) + +--- + +> _The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise._ + +— Edsger W. Dijkstra diff --git a/3/coursework/src/database/Cargo.toml b/3/coursework/src/database/Cargo.toml new file mode 100644 index 0000000..69edf73 --- /dev/null +++ b/3/coursework/src/database/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "database" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = "2.0.11" +derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "from", "into"] } +garde = { version = "0.22.0", features = ["email", "url", "derive"] } + +chrono = { version = "0.4.39", default-features = false, features = [ + "std", + "now", +] } +sqlx = { version = "0.8.3", default-features = false, features = [ + "mysql", + "macros", + "chrono", + "runtime-tokio", +] } diff --git a/3/coursework/src/database/compose.yaml b/3/coursework/src/database/compose.yaml new file mode 100644 index 0000000..5c2cec9 --- /dev/null +++ b/3/coursework/src/database/compose.yaml @@ -0,0 +1,11 @@ +services: + database: + image: mysql:9.0 + restart: unless-stopped + ports: + - "127.0.0.1:3306:3306" + volumes: + - ./data:/var/lib/mysql + - ./init:/docker-entrypoint-initdb.d/:ro + environment: + MYSQL_ROOT_PASSWORD: password # yes, I know diff --git a/3/coursework/src/database/init/init.sql b/3/coursework/src/database/init/init.sql new file mode 100644 index 0000000..8341d89 --- /dev/null +++ b/3/coursework/src/database/init/init.sql @@ -0,0 +1,177 @@ +-- DROP DATABASE IF EXISTS repository; +CREATE DATABASE repository; +USE repository; + +-- Required info for an account +CREATE TABLE Users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(31) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + + last_used TIMESTAMP NULL, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +-- Enables multiple packages to have the same base yet different components +CREATE TABLE PackageBases ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(127) UNIQUE NOT NULL, + description VARCHAR(510) NULL, + + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL +); + +-- User roles for working on packages: flagger, packager, submitter, maintainer, etc. +CREATE TABLE PackageBaseRoles ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(31) UNIQUE NOT NULL, + description VARCHAR(255) NULL +); +-- Roles that a user has for a package +CREATE TABLE PackageBaseUserRoles ( + base_id INT UNSIGNED, + user_id INT UNSIGNED, + role_id TINYINT UNSIGNED, + + comment VARCHAR(255) NULL, + + PRIMARY KEY (base_id, user_id, role_id), -- composite key + FOREIGN KEY (base_id) REFERENCES PackageBases(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE, + FOREIGN KEY (role_id) REFERENCES PackageBaseRoles(id) ON DELETE CASCADE +); + +-- Information about the actual packages +CREATE TABLE Packages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + package_base INT UNSIGNED NOT NULL, + name VARCHAR(127) UNIQUE NOT NULL, + version VARCHAR(127) NOT NULL, + description VARCHAR(255) NULL, + url VARCHAR(510) NULL, + + flagged_at TIMESTAMP NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (package_base) REFERENCES PackageBases (id) ON DELETE CASCADE +); + +-- depends, makedepends, optdepends, etc. +CREATE TABLE DependencyTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(31) UNIQUE NOT NULL +); +INSERT INTO DependencyTypes (id, name) VALUES +(1, 'depends'), +(2, 'makedepends'), +(3, 'checkdepends'), +(4, 'optdepends'); + +-- Track which dependencies a package has +CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + arch VARCHAR(63) NULL, + condition VARCHAR(255) NULL, + description VARCHAR(127) NULL, + + package INT UNSIGNED NOT NULL, + dependency_type TINYINT UNSIGNED NOT NULL, + dependency_package_name VARCHAR(127) NOT NULL, -- Not an actual package, but an an alias. Allows for package substitution. + + FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE, + FOREIGN KEY (dependency_type) REFERENCES DependencyTypes (id) +); + +-- conflicts, provides, replaces, etc. +CREATE TABLE RelationTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(31) UNIQUE NOT NULL +); +INSERT INTO RelationTypes (id, name) VALUES +(1, 'conflicts'), +(2, 'provides'), +(3, 'replaces'); + +-- Track which conflicts, provides and replaces a package has +CREATE TABLE PackageRelations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + arch VARCHAR(63) NULL, + condition VARCHAR(255) NULL, + + package INT UNSIGNED NOT NULL, + relation_type TINYINT UNSIGNED NOT NULL, + relation_package_name VARCHAR(127) NOT NULL, + + FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE, + FOREIGN KEY (relation_type) REFERENCES RelationTypes (id) +); + +-- Public user profile +/* CREATE TABLE UserProfiles ( user_id INT UNSIGNED PRIMARY KEY, + real_name VARCHAR(63) NULL, + homepage TEXT NULL, -- bio / description / whatever + irc_nick VARCHAR(31) NULL, + pgp_key CHAR(40) NULL, + language VARCHAR(31) NULL, -- only for display + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE +); */ + +-- Settings for the User +/* CREATE TABLE UserPreferences ( user_id INT UNSIGNED PRIMARY KEY, + inactive BOOLEAN DEFAULT 0 NOT NULL, -- user is no longer active + show_email BOOLEAN DEFAULT 0 NOT NULL, -- on public profile page + utc_timezone TINYINT DEFAULT 0 NOT NULL, -- adjust timestamps shown + backup_email VARCHAR(127) NULL, -- to restore the account + + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE +); */ + +-- Levels of access to the repository +/* CREATE TABLE AccessRoles ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(31) UNIQUE NOT NULL, + description VARCHAR(255) NULL +); */ +-- Roles that a user has +/* CREATE TABLE UserAccessRoles ( + user_id INT UNSIGNED, + role_id TINYINT UNSIGNED, + + PRIMARY KEY (user_id, role_id), -- composite key + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE, + FOREIGN KEY (role_id) REFERENCES AccessRoles(id) ON DELETE CASCADE +); */ + +-- Votes +/* CREATE TABLE PackageBaseUserVotes ( + package_base INT UNSIGNED, + user INT UNSIGNED, + score TINYINT UNSIGNED DEFAULT 0 NOT NULL CHECK (score <= 10), + + comment VARCHAR(255) NULL, + log TEXT NULL, -- error logs, etc. + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + PRIMARY KEY (package_base, user), -- composite key + FOREIGN KEY (package_base) REFERENCES PackageBases (id) ON DELETE CASCADE, + FOREIGN KEY (user) REFERENCES Users (id) ON DELETE CASCADE +); */ + +-- Information about licenses +/* CREATE TABLE Licenses ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(127) UNIQUE NOT NULL, + description TEXT NULL +); */ +-- Information about licenses +/* CREATE TABLE PackageLicenses ( + package INT UNSIGNED, + license INT UNSIGNED, + + PRIMARY KEY (package, license), -- composite key + FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE, + FOREIGN KEY (license) REFERENCES Licenses (id) ON DELETE CASCADE +); */ diff --git a/3/coursework/src/database/src/atomic.rs b/3/coursework/src/database/src/atomic.rs new file mode 100644 index 0000000..63721aa --- /dev/null +++ b/3/coursework/src/database/src/atomic.rs @@ -0,0 +1,40 @@ +type Result = std::result::Result>; + +#[allow(async_fn_in_trait)] +pub trait Atomic { + type Transaction<'a>; + + async fn start_transaction(&mut self) -> Result>; + async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result; + async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result; +} + +use sqlx::Connection; + +impl Atomic for sqlx::MySqlPool { + type Transaction<'a> = sqlx::MySqlTransaction<'a>; + + async fn start_transaction(&mut self) -> Result> { + self.begin().await.map_err(Box::from) + } + async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result { + transaction.rollback().await.map_err(Box::from) + } + async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result { + transaction.commit().await.map_err(Box::from) + } +} + +impl Atomic for sqlx::MySqlConnection { + type Transaction<'a> = sqlx::MySqlTransaction<'a>; + + async fn start_transaction(&mut self) -> Result> { + self.begin().await.map_err(Box::from) + } + async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result { + transaction.rollback().await.map_err(Box::from) + } + async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result { + transaction.commit().await.map_err(Box::from) + } +} diff --git a/3/coursework/src/database/src/connect.rs b/3/coursework/src/database/src/connect.rs new file mode 100644 index 0000000..b8bc3b5 --- /dev/null +++ b/3/coursework/src/database/src/connect.rs @@ -0,0 +1,44 @@ +type Result = std::result::Result>; + +#[allow(async_fn_in_trait)] +pub trait Connect { + type Connection; + + async fn open_connection(&self) -> Result; + async fn close_connection(connection: Self::Connection) -> Result; +} + +use sqlx::Connection; + +#[derive(Clone)] +pub struct MySqlPool { + pool: sqlx::MySqlPool, +} +impl Connect for MySqlPool { + type Connection = sqlx::MySqlPool; + + async fn open_connection(&self) -> Result { + Ok(self.pool.clone()) + } + async fn close_connection(_: Self::Connection) -> Result { + Ok(()) + } +} + +pub struct MySqlConnection { + link: String, +} + +impl Connect for MySqlConnection { + type Connection = sqlx::MySqlConnection; + + async fn open_connection(&self) -> Result { + sqlx::MySqlConnection::connect(&self.link) + .await + .map_err(Box::from) + } + async fn close_connection(connection: Self::Connection) -> Result { + connection.close().await?; + Ok(()) + } +} diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs new file mode 100644 index 0000000..d36d96d --- /dev/null +++ b/3/coursework/src/database/src/lib.rs @@ -0,0 +1,16 @@ +use garde::{Report, Unvalidated, Valid, Validate}; + +pub trait IntoValid: Validate { + fn into_valid(self) -> Result, Report> + where + Self: Sized, + ::Context: Default, + { + Unvalidated::new(self).validate() + } +} +impl IntoValid for T {} + +pub mod atomic; +pub mod connect; +pub mod repository; diff --git a/3/coursework/src/database/src/repository.rs b/3/coursework/src/database/src/repository.rs new file mode 100644 index 0000000..f950c0f --- /dev/null +++ b/3/coursework/src/database/src/repository.rs @@ -0,0 +1,2 @@ +pub mod session; +pub mod user; diff --git a/3/coursework/src/database/src/repository/session.rs b/3/coursework/src/database/src/repository/session.rs new file mode 100644 index 0000000..a670d60 --- /dev/null +++ b/3/coursework/src/database/src/repository/session.rs @@ -0,0 +1,51 @@ +use super::user::User; +pub type Result = std::result::Result>; + +use chrono::{DateTime, Utc}; +use derive_more::{Deref, DerefMut}; +use sqlx::{Executor, MySql}; + +#[allow(async_fn_in_trait)] +pub trait SessionRepository { + async fn start(connection: &mut C, user: User) -> Result; + async fn end(connection: &mut C, session: Session) -> Result; +} + +#[derive(DerefMut, Deref)] +pub struct Session { + #[deref] + #[deref_mut] + user: User, + + start: DateTime, +} + +impl Session { + pub const fn start(&self) -> DateTime { + self.start + } +} + +#[derive(Debug)] +pub struct SessionAdapter; + +impl SessionRepository for SessionAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + async fn start(connection: &mut E, user: User) -> Result { + let start = Utc::now(); + sqlx::query!( + "UPDATE Users SET last_used = ? WHERE id = ?", + start, + *user.id() + ) + .execute(&*connection) + .await?; + Ok(Session { user, start }) + } + + async fn end(_: &mut E, session: Session) -> Result { + Ok(session.user) + } +} diff --git a/3/coursework/src/database/src/repository/user.rs b/3/coursework/src/database/src/repository/user.rs new file mode 100644 index 0000000..a0a6566 --- /dev/null +++ b/3/coursework/src/database/src/repository/user.rs @@ -0,0 +1,164 @@ +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; +use sqlx::{Executor, MySql}; + +pub type Result = std::result::Result>; + +#[allow(async_fn_in_trait)] +pub trait UserRepository { + async fn get_by_id(connection: &C, id: Id) -> Result>; + async fn get_by_name(connection: &C, name: &Valid) -> Result>; + async fn get_by_email(connection: &C, email: &Valid) -> Result>; + + async fn change_name(connection: &mut C, user: &mut User, name: Valid) -> Result; + async fn change_email(connection: &mut C, user: &mut User, email: Valid) -> Result; + async fn change_password( + connection: &mut C, + user: &mut User, + password: Valid, + ) -> Result; + + async fn create(connection: &mut C, user: Valid) -> Result; +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(u32); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Username(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Email(#[garde(email, length(max = 255))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Password(#[garde(ascii, length(max = 255))] pub String); + +#[derive(Validate)] +pub struct UserData { + #[garde(dive)] + pub name: Username, + #[garde(dive)] + pub email: Email, + #[garde(dive)] + pub password: Password, +} + +#[derive(Deref)] +pub struct User { + id: Id, + #[deref] + data: UserData, +} +impl User { + pub const fn id(&self) -> Id { + self.id + } +} + +pub struct UserAdapter; + +struct QueryUser { + id: u32, + name: String, + email: String, + password: String, +} +impl From for User { + fn from(value: QueryUser) -> Self { + Self { + id: Id(value.id), + data: UserData { + name: Username(value.name), + email: Email(value.email), + password: Password(value.password), + }, + } + } +} + +impl UserRepository for UserAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + async fn get_by_id(connection: &E, id: Id) -> Result> { + Ok(sqlx::query_as!( + QueryUser, + "SELECT id, name, email, password FROM Users WHERE id = ?", + id.0 + ) + .fetch_optional(connection) + .await? + .map(Into::into)) + } + async fn get_by_name(connection: &E, name: &Valid) -> Result> { + Ok(sqlx::query_as!( + QueryUser, + "SELECT id, name, email, password FROM Users WHERE name = ?", + name.0 + ) + .fetch_optional(connection) + .await? + .map(Into::into)) + } + async fn get_by_email(connection: &E, email: &Valid) -> Result> { + Ok(sqlx::query_as!( + QueryUser, + "SELECT id, name, email, password FROM Users WHERE email = ?", + email.0 + ) + .fetch_optional(connection) + .await? + .map(Into::into)) + } + + async fn change_name(connection: &mut E, user: &mut User, name: Valid) -> Result { + sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) + .execute(&*connection) + .await?; + Ok(()) + } + async fn change_email(connection: &mut E, user: &mut User, email: Valid) -> Result { + sqlx::query!( + "UPDATE Users SET email = ? WHERE id = ?", + email.0, + user.id.0 + ) + .execute(&*connection) + .await?; + Ok(()) + } + async fn change_password( + connection: &mut E, + user: &mut User, + password: Valid, + ) -> Result { + sqlx::query!( + "UPDATE Users SET password = ? WHERE id = ?", + password.0, + user.id.0 + ) + .execute(&*connection) + .await?; + Ok(()) + } + + async fn create(connection: &mut E, user: Valid) -> Result { + let id = sqlx::query!( + "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", + user.name.0, + user.email.0, + user.password.0 + ) + .execute(&*connection) + .await? + .last_insert_id() as u32; + + Ok(User { + id: Id(id), + data: user.into_inner(), + }) + } +} From d799da520ed8586127f5a441e9edb63ea6d5ab23 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Thu, 30 Jan 2025 13:48:25 +0200 Subject: [PATCH 02/13] Separate ports & adaptors --- 3/coursework/src/database/init/init.sql | 4 +- 3/coursework/src/database/src/adapter.rs | 1 + .../src/database/src/adapter/mysql.rs | 2 + .../src/database/src/adapter/mysql/base.rs | 98 ++++++++++ .../src/database/src/adapter/mysql/user.rs | 125 +++++++++++++ 3/coursework/src/database/src/lib.rs | 3 +- 3/coursework/src/database/src/main.rs | 18 ++ 3/coursework/src/database/src/port.rs | 21 +++ 3/coursework/src/database/src/port/base.rs | 79 ++++++++ 3/coursework/src/database/src/port/package.rs | 168 ++++++++++++++++++ 3/coursework/src/database/src/port/user.rs | 105 +++++++++++ 3/coursework/src/database/src/repository.rs | 2 - .../src/database/src/repository/session.rs | 51 ------ .../src/database/src/repository/user.rs | 164 ----------------- 14 files changed, 621 insertions(+), 220 deletions(-) create mode 100644 3/coursework/src/database/src/adapter.rs create mode 100644 3/coursework/src/database/src/adapter/mysql.rs create mode 100644 3/coursework/src/database/src/adapter/mysql/base.rs create mode 100644 3/coursework/src/database/src/adapter/mysql/user.rs create mode 100644 3/coursework/src/database/src/main.rs create mode 100644 3/coursework/src/database/src/port.rs create mode 100644 3/coursework/src/database/src/port/base.rs create mode 100644 3/coursework/src/database/src/port/package.rs create mode 100644 3/coursework/src/database/src/port/user.rs delete mode 100644 3/coursework/src/database/src/repository.rs delete mode 100644 3/coursework/src/database/src/repository/session.rs delete mode 100644 3/coursework/src/database/src/repository/user.rs diff --git a/3/coursework/src/database/init/init.sql b/3/coursework/src/database/init/init.sql index 8341d89..66dfbb0 100644 --- a/3/coursework/src/database/init/init.sql +++ b/3/coursework/src/database/init/init.sql @@ -70,7 +70,7 @@ INSERT INTO DependencyTypes (id, name) VALUES -- Track which dependencies a package has CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, - condition VARCHAR(255) NULL, + requirement VARCHAR(255) NULL, description VARCHAR(127) NULL, package INT UNSIGNED NOT NULL, @@ -93,7 +93,7 @@ INSERT INTO RelationTypes (id, name) VALUES -- Track which conflicts, provides and replaces a package has CREATE TABLE PackageRelations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, - condition VARCHAR(255) NULL, + requiremen VARCHAR(255) NULL, package INT UNSIGNED NOT NULL, relation_type TINYINT UNSIGNED NOT NULL, diff --git a/3/coursework/src/database/src/adapter.rs b/3/coursework/src/database/src/adapter.rs new file mode 100644 index 0000000..4fb2dd9 --- /dev/null +++ b/3/coursework/src/database/src/adapter.rs @@ -0,0 +1 @@ +pub mod mysql; diff --git a/3/coursework/src/database/src/adapter/mysql.rs b/3/coursework/src/database/src/adapter/mysql.rs new file mode 100644 index 0000000..5e21c1d --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql.rs @@ -0,0 +1,2 @@ +pub mod user; +pub mod base; diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/database/src/adapter/mysql/base.rs new file mode 100644 index 0000000..559c6e3 --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql/base.rs @@ -0,0 +1,98 @@ +pub use crate::port::base::*; + +use sqlx::{Executor, MySql}; + +pub struct BaseAdapter; + +struct DatabaseBase { + id: u64, + name: String, + description: Option, + created_at: DateTime, + updated_at: DateTime, +} +impl From for Base { + fn from(value: DatabaseBase) -> Self { + Self { + id: Id(value.id), + name: value.name.into(), + description: value.description.into(), + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + +impl BaseRepository for BaseAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl crate::port::CRUD for BaseAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + type Create = New; + type Read = Id; + type Update = Field; + type Delete = Id; + type Existing = Base; + type Id = Id; + + async fn create(connection: &mut E, data: Self::Create) -> Result { + Ok(Id(sqlx::query!( + "INSERT INTO PackageBases (name, description) VALUES (?, ?)", + data.name.0, + data.description.0, + ) + .execute(&*connection) + .await? + .last_insert_id())) + } + + async fn read(connection: &E, data: Self::Read) -> Result> { + Ok(sqlx::query_as!( + DatabaseBase, + "SELECT * FROM PackageBases WHERE id = ?", + data.0 + ) + .fetch_optional(connection) + .await? + .map(Into::into)) + } + + async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { + match data { + Field::Name(valid) => { + sqlx::query!( + "UPDATE PackageBases SET name = ? WHERE id = ?", + valid.0, + id.0 + ) + } + Field::Description(valid) => { + sqlx::query!( + "UPDATE PackageBases SET description = ? WHERE id = ?", + valid.0, + id.0 + ) + } + Field::CreatedAt(date_time) => sqlx::query!( + "UPDATE PackageBases SET created_at = ? WHERE id = ?", + date_time, + id.0 + ), + Field::UpdatedAt(date_time) => sqlx::query!( + "UPDATE PackageBases SET updated_at = ? WHERE id = ?", + date_time, + id.0 + ), + } + .execute(&*connection) + .await?; + Ok(()) + } + + async fn delete(connection: &mut E, data: Self::Delete) -> Result { + sqlx::query!("DELETE FROM PackageBases WHERE id = ?", data.0) + .execute(&*connection) + .await?; + Ok(()) + } +} diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/database/src/adapter/mysql/user.rs new file mode 100644 index 0000000..0dada5f --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql/user.rs @@ -0,0 +1,125 @@ +pub use crate::port::user::*; + +use sqlx::{Executor, MySql}; + +pub struct UserAdapter; + +struct DatabaseUser { + id: u64, + name: String, + email: String, + password: String, + last_used: Option>, + created_at: DateTime, + updated_at: DateTime, +} + +impl From for User { + fn from(value: DatabaseUser) -> Self { + Self { + id: Id(value.id), + name: value.name.into(), + email: value.email.into(), + password: value.password.into(), + last_used: value.last_used, + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + +impl UserRepository for UserAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl crate::port::CRUD for UserAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + type Create = New; + type Read = Unique; + type Update = Field; + type Delete = Unique; + type Existing = User; + type Id = Id; + + async fn create(connection: &mut E, data: Self::Create) -> Result { + Ok(Id(sqlx::query!( + "INSERT INTO Users (name, email, password, last_used) VALUES (?, ?, ?, ?)", + data.name.0, + data.email.0, + data.password.0, + data.last_used, + ) + .execute(&*connection) + .await? + .last_insert_id())) + } + + async fn read(connection: &E, data: Self::Read) -> Result> { + Ok(match data { + Unique::Id(id) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE id = ?", id.0) + .fetch_optional(connection) + .await + } + Unique::Name(name) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE name = ?", name.0) + .fetch_optional(connection) + .await + } + Unique::Email(email) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE email = ?", email.0) + .fetch_optional(connection) + .await + } + }? + .map(Into::into)) + } + + async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { + match data { + Field::Name(valid) => { + sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", valid.0, id.0) + } + Field::Email(valid) => { + sqlx::query!("UPDATE Users SET email = ? WHERE id = ?", valid.0, id.0) + } + Field::Password(valid) => { + sqlx::query!("UPDATE Users SET password = ? WHERE id = ?", valid.0, id.0) + } + Field::LastUsed(date_time) => { + sqlx::query!( + "UPDATE Users SET last_used = ? WHERE id = ?", + date_time, + id.0 + ) + } + Field::CreatedAt(date_time) => sqlx::query!( + "UPDATE Users SET created_at = ? WHERE id = ?", + date_time, + id.0 + ), + Field::UpdatedAt(date_time) => sqlx::query!( + "UPDATE Users SET updated_at = ? WHERE id = ?", + date_time, + id.0 + ), + } + .execute(&*connection) + .await?; + Ok(()) + } + + async fn delete(connection: &mut E, data: Self::Delete) -> Result { + match data { + Unique::Id(id) => sqlx::query!("DELETE FROM Users WHERE id = ?", id.0), + Unique::Name(name) => { + sqlx::query!("DELETE FROM Users WHERE name = ?", name.0) + } + Unique::Email(email) => { + sqlx::query!("DELETE FROM Users WHERE email = ?", email.0) + } + } + .execute(&*connection) + .await?; + Ok(()) + } +} diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs index d36d96d..da8cf3a 100644 --- a/3/coursework/src/database/src/lib.rs +++ b/3/coursework/src/database/src/lib.rs @@ -13,4 +13,5 @@ impl IntoValid for T {} pub mod atomic; pub mod connect; -pub mod repository; +pub mod port; +pub mod adapter; diff --git a/3/coursework/src/database/src/main.rs b/3/coursework/src/database/src/main.rs new file mode 100644 index 0000000..eb43d13 --- /dev/null +++ b/3/coursework/src/database/src/main.rs @@ -0,0 +1,18 @@ +use garde::{Valid, Validate}; + +#[derive(Validate)] +enum Data { + Struct { + #[garde(range(min=-10, max=10))] + field: i32, + }, + Tuple(#[garde(ascii)] String), +} + +fn main() { + let data = Data::Struct { field: 100 }; + let data = Data::Tuple("🧱".into()); + if let Err(e) = data.validate() { + println!("invalid data: {e}"); + } +} diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs new file mode 100644 index 0000000..c0b5d0c --- /dev/null +++ b/3/coursework/src/database/src/port.rs @@ -0,0 +1,21 @@ +pub type Result = std::result::Result>; + +#[allow(async_fn_in_trait)] +pub trait CRUD { + type Create; + type Read; + type Update; + type Delete; + type Existing; + type Id; + + async fn create(connection: &mut C, data: Self::Create) -> Result; + async fn read(connection: &C, data: Self::Read) -> Result>; + async fn update(connection: &mut C, id: Self::Id, data: Self::Update) -> Result; + async fn delete(connection: &mut C, data: Self::Delete) -> Result; +} + +pub mod user; +pub mod base; +// pub mod package; +// pub mod session; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs new file mode 100644 index 0000000..f241124 --- /dev/null +++ b/3/coursework/src/database/src/port/base.rs @@ -0,0 +1,79 @@ +pub use super::{CRUD, Result}; + +pub use chrono::{DateTime, Utc}; +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; + +#[allow(async_fn_in_trait)] +pub trait BaseRepository: + super::CRUD< + C, + Create = New, + Read = Id, + Update = Field, + Delete = Id, + Existing = Base, + Id = Id, + > +{ + async fn update_base(connection: &mut C, base: &mut Base, data: Self::Update) -> Result { + Self::update(connection, base.id, data.clone()).await?; + match data { + Field::Name(valid) => base.name = valid.into_inner(), + Field::Description(valid) => base.description = valid.into_inner(), + Field::CreatedAt(date_time) => base.created_at = date_time, + Field::UpdatedAt(date_time) => base.updated_at = date_time, + } + Ok(()) + } +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(pub(crate) u64); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Description(#[garde(length(max = 510))] pub Option); + +pub struct New { + pub name: Name, + pub description: Description, +} + +#[derive(Clone)] +pub enum Field { + Name(Valid), + Description(Valid), + CreatedAt(DateTime), + UpdatedAt(DateTime), +} + +pub struct Base { + pub(crate) id: Id, + pub(crate) name: Name, + pub(crate) description: Description, + pub(crate) created_at: DateTime, + pub(crate) updated_at: DateTime, +} + +impl Base { + pub fn id(&self) -> Id { + self.id + } + pub fn name(&self) -> &Name { + &self.name + } + pub fn description(&self) -> &Description { + &self.description + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn updated_at(&self) -> DateTime { + self.updated_at + } +} diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs new file mode 100644 index 0000000..72de830 --- /dev/null +++ b/3/coursework/src/database/src/port/package.rs @@ -0,0 +1,168 @@ +use super::{Result, package_base::Base}; + +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; +use sqlx::{Executor, MySql}; + +// pub enum GetBy + +#[allow(async_fn_in_trait)] +pub trait PackageRepository { + async fn get_by_id(connection: &C, id: Id) -> Result>; + async fn get_by_name(connection: &C, name: &Valid) -> Result>; + + async fn change_name(connection: &mut C, package: &mut Package, name: Valid) -> Result; + async fn change_base(connection: &mut C, package: &mut Package, base: &Base) -> Result; + async fn change_version(connection: &mut C, package: &mut Package, version: Valid) -> Result; + + async fn create(connection: &mut C, data: Valid) -> Result; +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(u32); + +pub type BaseId = super::package_base::Id; + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Version(#[garde(alphanumeric, length(min = 1, max = 127))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Description(#[garde(ascii, length(max = 255))] pub Option); + +#[derive(Validate)] +#[garde(transparent)] +pub struct URL(#[garde(url, length(max = 510))] pub Option); + +#[derive(Validate)] +pub struct PackageData { + #[garde(dive)] + pub name: Name, + #[garde(dive)] + pub description: Description, +} + +#[derive(Deref)] +pub struct Package { + id: Id, + #[deref] + data: PackageData, +} +impl Package { + pub const fn id(&self) -> Id { + self.id + } +} + +// pub struct UserAdapter; +// +// struct QueryUser { +// id: u32, +// name: String, +// email: String, +// password: String, +// } +// impl From for Package { +// fn from(value: QueryUser) -> Self { +// Self { +// id: Id(value.id), +// data: PackageData { +// name: Name(value.name), +// description: Description(value.email), +// }, +// } +// } +// } +// +// impl PackageRepository for UserAdapter +// where +// for<'a> &'a E: Executor<'a, Database = MySql>, +// { +// async fn get_by_id(connection: &E, id: Id) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE id = ?", +// id.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// async fn get_by_name(connection: &E, name: &Valid) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE name = ?", +// name.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// async fn get_by_email(connection: &E, email: &Valid) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE email = ?", +// email.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// +// async fn change_name(connection: &mut E, user: &mut Package, name: Valid) -> Result { +// sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// async fn change_email( +// connection: &mut E, +// user: &mut Package, +// email: Valid, +// ) -> Result { +// sqlx::query!( +// "UPDATE Users SET email = ? WHERE id = ?", +// email.0, +// user.id.0 +// ) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// async fn change_password( +// connection: &mut E, +// user: &mut Package, +// password: Valid, +// ) -> Result { +// sqlx::query!( +// "UPDATE Users SET password = ? WHERE id = ?", +// password.0, +// user.id.0 +// ) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// +// async fn create(connection: &mut E, data: Valid) -> Result { +// let id = sqlx::query!( +// "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", +// data.name.0, +// data.description.0, +// data.password.0 +// ) +// .execute(&*connection) +// .await? +// .last_insert_id() as u32; +// +// Ok(Package { +// id: Id(id), +// data: data.into_inner(), +// }) +// } +// } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs new file mode 100644 index 0000000..afa448b --- /dev/null +++ b/3/coursework/src/database/src/port/user.rs @@ -0,0 +1,105 @@ +pub use super::{CRUD, Result}; + +pub use chrono::{DateTime, Utc}; +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; + +#[allow(async_fn_in_trait)] +pub trait UserRepository: + super::CRUD< + C, + Create = New, + Read = Unique, + Update = Field, + Delete = Unique, + Existing = User, + Id = Id, + > +{ + async fn update_user(connection: &mut C, user: &mut User, data: Self::Update) -> Result { + Self::update(connection, user.id, data.clone()).await?; + match data { + Field::Name(valid) => user.name = valid.into_inner(), + Field::Email(valid) => user.email = valid.into_inner(), + Field::Password(valid) => user.password = valid.into_inner(), + Field::LastUsed(date_time) => user.last_used = date_time, + Field::CreatedAt(date_time) => user.created_at = date_time, + Field::UpdatedAt(date_time) => user.updated_at = date_time, + } + Ok(()) + } +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(pub(crate) u64); + +// TODO: is this the right layer for requirements (email) validatoin? + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Email(#[garde(email, length(max = 255))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Password(#[garde(ascii, length(max = 255))] pub String); + +pub struct New { + pub name: Valid, + pub email: Valid, + pub password: Valid, + pub last_used: Option>, +} + +pub enum Unique { + Id(Id), + Name(Valid), + Email(Valid), +} + +#[derive(Clone)] +pub enum Field { + Name(Valid), + Email(Valid), + Password(Valid), + LastUsed(Option>), + CreatedAt(DateTime), + UpdatedAt(DateTime), +} + +pub struct User { + pub(crate) id: Id, + pub(crate) name: Name, + pub(crate) email: Email, + pub(crate) password: Password, + pub(crate) last_used: Option>, + pub(crate) created_at: DateTime, + pub(crate) updated_at: DateTime, +} + +impl User { + pub const fn id(&self) -> Id { + self.id + } + pub const fn name(&self) -> &Name { + &self.name + } + pub const fn email(&self) -> &Email { + &self.email + } + pub const fn password(&self) -> &Password { + &self.password + } + pub const fn last_used(&self) -> Option> { + self.last_used + } + pub const fn created_at(&self) -> DateTime { + self.created_at + } + pub const fn updated_at(&self) -> DateTime { + self.updated_at + } +} diff --git a/3/coursework/src/database/src/repository.rs b/3/coursework/src/database/src/repository.rs deleted file mode 100644 index f950c0f..0000000 --- a/3/coursework/src/database/src/repository.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod session; -pub mod user; diff --git a/3/coursework/src/database/src/repository/session.rs b/3/coursework/src/database/src/repository/session.rs deleted file mode 100644 index a670d60..0000000 --- a/3/coursework/src/database/src/repository/session.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::user::User; -pub type Result = std::result::Result>; - -use chrono::{DateTime, Utc}; -use derive_more::{Deref, DerefMut}; -use sqlx::{Executor, MySql}; - -#[allow(async_fn_in_trait)] -pub trait SessionRepository { - async fn start(connection: &mut C, user: User) -> Result; - async fn end(connection: &mut C, session: Session) -> Result; -} - -#[derive(DerefMut, Deref)] -pub struct Session { - #[deref] - #[deref_mut] - user: User, - - start: DateTime, -} - -impl Session { - pub const fn start(&self) -> DateTime { - self.start - } -} - -#[derive(Debug)] -pub struct SessionAdapter; - -impl SessionRepository for SessionAdapter -where - for<'a> &'a E: Executor<'a, Database = MySql>, -{ - async fn start(connection: &mut E, user: User) -> Result { - let start = Utc::now(); - sqlx::query!( - "UPDATE Users SET last_used = ? WHERE id = ?", - start, - *user.id() - ) - .execute(&*connection) - .await?; - Ok(Session { user, start }) - } - - async fn end(_: &mut E, session: Session) -> Result { - Ok(session.user) - } -} diff --git a/3/coursework/src/database/src/repository/user.rs b/3/coursework/src/database/src/repository/user.rs deleted file mode 100644 index a0a6566..0000000 --- a/3/coursework/src/database/src/repository/user.rs +++ /dev/null @@ -1,164 +0,0 @@ -use derive_more::{Deref, From, Into}; -use garde::{Valid, Validate}; -use sqlx::{Executor, MySql}; - -pub type Result = std::result::Result>; - -#[allow(async_fn_in_trait)] -pub trait UserRepository { - async fn get_by_id(connection: &C, id: Id) -> Result>; - async fn get_by_name(connection: &C, name: &Valid) -> Result>; - async fn get_by_email(connection: &C, email: &Valid) -> Result>; - - async fn change_name(connection: &mut C, user: &mut User, name: Valid) -> Result; - async fn change_email(connection: &mut C, user: &mut User, email: Valid) -> Result; - async fn change_password( - connection: &mut C, - user: &mut User, - password: Valid, - ) -> Result; - - async fn create(connection: &mut C, user: Valid) -> Result; -} - -#[derive(Deref, Into, Clone, Copy)] -pub struct Id(u32); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Username(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Email(#[garde(email, length(max = 255))] pub String); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Password(#[garde(ascii, length(max = 255))] pub String); - -#[derive(Validate)] -pub struct UserData { - #[garde(dive)] - pub name: Username, - #[garde(dive)] - pub email: Email, - #[garde(dive)] - pub password: Password, -} - -#[derive(Deref)] -pub struct User { - id: Id, - #[deref] - data: UserData, -} -impl User { - pub const fn id(&self) -> Id { - self.id - } -} - -pub struct UserAdapter; - -struct QueryUser { - id: u32, - name: String, - email: String, - password: String, -} -impl From for User { - fn from(value: QueryUser) -> Self { - Self { - id: Id(value.id), - data: UserData { - name: Username(value.name), - email: Email(value.email), - password: Password(value.password), - }, - } - } -} - -impl UserRepository for UserAdapter -where - for<'a> &'a E: Executor<'a, Database = MySql>, -{ - async fn get_by_id(connection: &E, id: Id) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE id = ?", - id.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - async fn get_by_name(connection: &E, name: &Valid) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE name = ?", - name.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - async fn get_by_email(connection: &E, email: &Valid) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE email = ?", - email.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - - async fn change_name(connection: &mut E, user: &mut User, name: Valid) -> Result { - sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) - .execute(&*connection) - .await?; - Ok(()) - } - async fn change_email(connection: &mut E, user: &mut User, email: Valid) -> Result { - sqlx::query!( - "UPDATE Users SET email = ? WHERE id = ?", - email.0, - user.id.0 - ) - .execute(&*connection) - .await?; - Ok(()) - } - async fn change_password( - connection: &mut E, - user: &mut User, - password: Valid, - ) -> Result { - sqlx::query!( - "UPDATE Users SET password = ? WHERE id = ?", - password.0, - user.id.0 - ) - .execute(&*connection) - .await?; - Ok(()) - } - - async fn create(connection: &mut E, user: Valid) -> Result { - let id = sqlx::query!( - "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", - user.name.0, - user.email.0, - user.password.0 - ) - .execute(&*connection) - .await? - .last_insert_id() as u32; - - Ok(User { - id: Id(id), - data: user.into_inner(), - }) - } -} From 883b87e5931a23a8cc06419d8b5583a26ed3d6ec Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Thu, 30 Jan 2025 20:37:59 +0200 Subject: [PATCH 03/13] Simplify CRUD trait & Package repository --- .../src/database/src/adapter/mysql.rs | 3 +- .../src/database/src/adapter/mysql/base.rs | 93 ++++---- .../src/database/src/adapter/mysql/package.rs | 150 ++++++++++++ .../src/database/src/adapter/mysql/user.rs | 115 +++++----- 3/coursework/src/database/src/lib.rs | 2 +- 3/coursework/src/database/src/main.rs | 18 -- 3/coursework/src/database/src/port.rs | 23 +- 3/coursework/src/database/src/port/base.rs | 61 ++--- 3/coursework/src/database/src/port/package.rs | 216 ++++++------------ 3/coursework/src/database/src/port/user.rs | 72 ++---- 10 files changed, 390 insertions(+), 363 deletions(-) create mode 100644 3/coursework/src/database/src/adapter/mysql/package.rs delete mode 100644 3/coursework/src/database/src/main.rs diff --git a/3/coursework/src/database/src/adapter/mysql.rs b/3/coursework/src/database/src/adapter/mysql.rs index 5e21c1d..ec9df17 100644 --- a/3/coursework/src/database/src/adapter/mysql.rs +++ b/3/coursework/src/database/src/adapter/mysql.rs @@ -1,2 +1,3 @@ -pub mod user; pub mod base; +pub mod package; +pub mod user; diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/database/src/adapter/mysql/base.rs index 559c6e3..347013e 100644 --- a/3/coursework/src/database/src/adapter/mysql/base.rs +++ b/3/coursework/src/database/src/adapter/mysql/base.rs @@ -4,95 +4,94 @@ use sqlx::{Executor, MySql}; pub struct BaseAdapter; -struct DatabaseBase { - id: u64, - name: String, - description: Option, - created_at: DateTime, - updated_at: DateTime, -} -impl From for Base { - fn from(value: DatabaseBase) -> Self { - Self { - id: Id(value.id), - name: value.name.into(), - description: value.description.into(), - created_at: value.created_at, - updated_at: value.updated_at, - } - } -} - impl BaseRepository for BaseAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} impl crate::port::CRUD for BaseAdapter where for<'a> &'a E: Executor<'a, Database = MySql>, { - type Create = New; - type Read = Id; + type New = New; + type Unique = u64; type Update = Field; - type Delete = Id; type Existing = Base; - type Id = Id; - async fn create(connection: &mut E, data: Self::Create) -> Result { - Ok(Id(sqlx::query!( - "INSERT INTO PackageBases (name, description) VALUES (?, ?)", + async fn create(connection: &mut E, data: Self::New) -> Result { + let created_at = Utc::now(); + let id = sqlx::query!( + "INSERT INTO PackageBases (name, description, created_at, updated_at) VALUES (?, ?, ?, ?)", data.name.0, data.description.0, + created_at, created_at, ) .execute(&*connection) .await? - .last_insert_id())) + .last_insert_id(); + + Ok(Self::Existing { + id, + name: data.name.into_inner().0, + description: data.description.into_inner().0, + created_at, + updated_at: created_at, + }) } - async fn read(connection: &E, data: Self::Read) -> Result> { - Ok(sqlx::query_as!( - DatabaseBase, - "SELECT * FROM PackageBases WHERE id = ?", - data.0 + async fn read(connection: &E, data: Self::Unique) -> Result> { + Ok( + sqlx::query_as!(Base, "SELECT * FROM PackageBases WHERE id = ?", data) + .fetch_optional(connection) + .await?, ) - .fetch_optional(connection) - .await? - .map(Into::into)) } - async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { - match data { - Field::Name(valid) => { + async fn update( + connection: &mut E, + existing: &mut Self::Existing, + data: Self::Update, + ) -> Result { + match &data { + Field::Name(name) => { sqlx::query!( "UPDATE PackageBases SET name = ? WHERE id = ?", - valid.0, - id.0 + name.0, + existing.id ) } - Field::Description(valid) => { + Field::Description(description) => { sqlx::query!( "UPDATE PackageBases SET description = ? WHERE id = ?", - valid.0, - id.0 + description.0, + existing.id ) } Field::CreatedAt(date_time) => sqlx::query!( "UPDATE PackageBases SET created_at = ? WHERE id = ?", date_time, - id.0 + existing.id ), Field::UpdatedAt(date_time) => sqlx::query!( "UPDATE PackageBases SET updated_at = ? WHERE id = ?", date_time, - id.0 + existing.id ), } .execute(&*connection) .await?; + + match data { + Field::Name(valid) => existing.name = valid.into_inner().0, + Field::Description(valid) => existing.description = valid.into_inner().0, + Field::CreatedAt(date_time) => existing.created_at = date_time, + Field::UpdatedAt(date_time) => existing.updated_at = date_time, + } + Ok(()) } - async fn delete(connection: &mut E, data: Self::Delete) -> Result { - sqlx::query!("DELETE FROM PackageBases WHERE id = ?", data.0) + async fn delete(connection: &mut E, data: Self::Unique) -> Result { + sqlx::query!("DELETE FROM PackageBases WHERE id = ?", data) .execute(&*connection) .await?; + Ok(()) } } diff --git a/3/coursework/src/database/src/adapter/mysql/package.rs b/3/coursework/src/database/src/adapter/mysql/package.rs new file mode 100644 index 0000000..7cb483d --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql/package.rs @@ -0,0 +1,150 @@ +pub use crate::port::package::*; + +use sqlx::{Executor, MySql}; + +pub struct PackageAdapter; + +impl PackageRepository for PackageAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl crate::port::CRUD for PackageAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + type New = New; + type Update = Field; + type Unique = Unique; + type Existing = Package; + + async fn create(connection: &mut E, data: Self::New) -> Result { + let created_at = Utc::now(); + let id = sqlx::query!( + "INSERT INTO Packages \ + (package_base, name, version, description, url, flagged_at, created_at, updated_at) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + data.package_base.id, + data.name.0, + data.version.0, + data.description.0, + data.url.0, + data.flagged_at, + created_at, + created_at, + ) + .execute(&*connection) + .await? + .last_insert_id(); + + Ok(Self::Existing { + id, + package_base: data.package_base.id, + name: data.name.into_inner().0, + version: data.version.into_inner().0, + description: data.description.into_inner().0, + url: data.url.into_inner().0, + flagged_at: data.flagged_at, + created_at, + updated_at: created_at, + }) + } + + async fn read(connection: &E, data: Self::Unique) -> Result> { + Ok(match data { + Unique::Id(id) => { + sqlx::query_as!(Package, "SELECT * FROM Packages WHERE id = ?", id) + .fetch_optional(connection) + .await + } + Unique::Name(name) => { + sqlx::query_as!(Package, "SELECT * FROM Packages WHERE name = ?", name.0) + .fetch_optional(connection) + .await + } + }?) + } + + async fn update( + connection: &mut E, + existing: &mut Self::Existing, + data: Self::Update, + ) -> Result { + match &data { + Field::Name(name) => { + sqlx::query!( + "UPDATE Packages SET name = ? WHERE id = ?", + name.0, + existing.id + ) + } + Field::PackageBase(package_base) => { + sqlx::query!( + "UPDATE Packages SET package_base = ? WHERE id = ?", + package_base.id, + existing.id + ) + } + Field::Version(version) => { + sqlx::query!( + "UPDATE Packages SET version = ? WHERE id = ?", + version.0, + existing.id + ) + } + Field::Description(description) => { + sqlx::query!( + "UPDATE Packages SET description = ? WHERE id = ?", + description.0, + existing.id + ) + } + Field::URL(url) => { + sqlx::query!( + "UPDATE Packages SET url = ? WHERE id = ?", + url.0, + existing.id + ) + } + Field::FlaggedAt(date_time) => sqlx::query!( + "UPDATE Packages SET flagged_at = ? WHERE id = ?", + date_time, + existing.id + ), + Field::CreatedAt(date_time) => sqlx::query!( + "UPDATE Packages SET created_at = ? WHERE id = ?", + date_time, + existing.id + ), + Field::UpdatedAt(date_time) => sqlx::query!( + "UPDATE Packages SET updated_at = ? WHERE id = ?", + date_time, + existing.id + ), + } + .execute(&*connection) + .await?; + + match data { + Field::Name(valid) => existing.name = valid.into_inner().0, + Field::PackageBase(base) => existing.package_base = base.id, + Field::Version(valid) => existing.version = valid.into_inner().0, + Field::Description(valid) => existing.description = valid.into_inner().0, + Field::URL(valid) => existing.url = valid.into_inner().0, + Field::FlaggedAt(date_time) => existing.flagged_at = date_time, + Field::CreatedAt(date_time) => existing.created_at = date_time, + Field::UpdatedAt(date_time) => existing.updated_at = date_time, + } + + Ok(()) + } + + async fn delete(connection: &mut E, data: Self::Unique) -> Result { + match data { + Unique::Id(id) => sqlx::query!("DELETE FROM Packages WHERE id = ?", id), + Unique::Name(name) => { + sqlx::query!("DELETE FROM Packages WHERE name = ?", name.0) + } + } + .execute(&*connection) + .await?; + + Ok(()) + } +} diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/database/src/adapter/mysql/user.rs index 0dada5f..60d0df3 100644 --- a/3/coursework/src/database/src/adapter/mysql/user.rs +++ b/3/coursework/src/database/src/adapter/mysql/user.rs @@ -4,113 +4,125 @@ use sqlx::{Executor, MySql}; pub struct UserAdapter; -struct DatabaseUser { - id: u64, - name: String, - email: String, - password: String, - last_used: Option>, - created_at: DateTime, - updated_at: DateTime, -} - -impl From for User { - fn from(value: DatabaseUser) -> Self { - Self { - id: Id(value.id), - name: value.name.into(), - email: value.email.into(), - password: value.password.into(), - last_used: value.last_used, - created_at: value.created_at, - updated_at: value.updated_at, - } - } -} - impl UserRepository for UserAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} impl crate::port::CRUD for UserAdapter where for<'a> &'a E: Executor<'a, Database = MySql>, { - type Create = New; - type Read = Unique; + type New = New; type Update = Field; - type Delete = Unique; + type Unique = Unique; type Existing = User; - type Id = Id; - async fn create(connection: &mut E, data: Self::Create) -> Result { - Ok(Id(sqlx::query!( - "INSERT INTO Users (name, email, password, last_used) VALUES (?, ?, ?, ?)", + async fn create(connection: &mut E, data: Self::New) -> Result { + let created_at = Utc::now(); + let id = sqlx::query!( + "INSERT INTO Users (name, email, password, last_used, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", data.name.0, data.email.0, data.password.0, data.last_used, + created_at, + created_at, ) .execute(&*connection) .await? - .last_insert_id())) + .last_insert_id(); + + Ok(Self::Existing { + id, + name: data.name.into_inner().0, + email: data.email.into_inner().0, + password: data.password.into_inner().0, + last_used: data.last_used, + created_at, + updated_at: created_at, + }) } - async fn read(connection: &E, data: Self::Read) -> Result> { + async fn read(connection: &E, data: Self::Unique) -> Result> { Ok(match data { Unique::Id(id) => { - sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE id = ?", id.0) + sqlx::query_as!(User, "SELECT * FROM Users WHERE id = ?", id) .fetch_optional(connection) .await } Unique::Name(name) => { - sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE name = ?", name.0) + sqlx::query_as!(User, "SELECT * FROM Users WHERE name = ?", name.0) .fetch_optional(connection) .await } Unique::Email(email) => { - sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE email = ?", email.0) + sqlx::query_as!(User, "SELECT * FROM Users WHERE email = ?", email.0) .fetch_optional(connection) .await } - }? - .map(Into::into)) + }?) } - async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { - match data { - Field::Name(valid) => { - sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", valid.0, id.0) + async fn update( + connection: &mut E, + existing: &mut Self::Existing, + data: Self::Update, + ) -> Result { + match &data { + Field::Name(name) => { + sqlx::query!( + "UPDATE Users SET name = ? WHERE id = ?", + name.0, + existing.id + ) } - Field::Email(valid) => { - sqlx::query!("UPDATE Users SET email = ? WHERE id = ?", valid.0, id.0) + Field::Email(email) => { + sqlx::query!( + "UPDATE Users SET email = ? WHERE id = ?", + email.0, + existing.id + ) } - Field::Password(valid) => { - sqlx::query!("UPDATE Users SET password = ? WHERE id = ?", valid.0, id.0) + Field::Password(password) => { + sqlx::query!( + "UPDATE Users SET password = ? WHERE id = ?", + password.0, + existing.id + ) } Field::LastUsed(date_time) => { sqlx::query!( "UPDATE Users SET last_used = ? WHERE id = ?", date_time, - id.0 + existing.id ) } Field::CreatedAt(date_time) => sqlx::query!( "UPDATE Users SET created_at = ? WHERE id = ?", date_time, - id.0 + existing.id ), Field::UpdatedAt(date_time) => sqlx::query!( "UPDATE Users SET updated_at = ? WHERE id = ?", date_time, - id.0 + existing.id ), } .execute(&*connection) .await?; + + match data { + Field::Name(valid) => existing.name = valid.into_inner().0, + Field::Email(valid) => existing.email = valid.into_inner().0, + Field::Password(valid) => existing.password = valid.into_inner().0, + Field::LastUsed(date_time) => existing.last_used = date_time, + Field::CreatedAt(date_time) => existing.created_at = date_time, + Field::UpdatedAt(date_time) => existing.updated_at = date_time, + } + Ok(()) } - async fn delete(connection: &mut E, data: Self::Delete) -> Result { + async fn delete(connection: &mut E, data: Self::Unique) -> Result { match data { - Unique::Id(id) => sqlx::query!("DELETE FROM Users WHERE id = ?", id.0), + Unique::Id(id) => sqlx::query!("DELETE FROM Users WHERE id = ?", id), Unique::Name(name) => { sqlx::query!("DELETE FROM Users WHERE name = ?", name.0) } @@ -120,6 +132,7 @@ where } .execute(&*connection) .await?; + Ok(()) } } diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs index da8cf3a..86d563e 100644 --- a/3/coursework/src/database/src/lib.rs +++ b/3/coursework/src/database/src/lib.rs @@ -11,7 +11,7 @@ pub trait IntoValid: Validate { } impl IntoValid for T {} +pub mod adapter; pub mod atomic; pub mod connect; pub mod port; -pub mod adapter; diff --git a/3/coursework/src/database/src/main.rs b/3/coursework/src/database/src/main.rs deleted file mode 100644 index eb43d13..0000000 --- a/3/coursework/src/database/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -use garde::{Valid, Validate}; - -#[derive(Validate)] -enum Data { - Struct { - #[garde(range(min=-10, max=10))] - field: i32, - }, - Tuple(#[garde(ascii)] String), -} - -fn main() { - let data = Data::Struct { field: 100 }; - let data = Data::Tuple("🧱".into()); - if let Err(e) = data.validate() { - println!("invalid data: {e}"); - } -} diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs index c0b5d0c..d2e13cf 100644 --- a/3/coursework/src/database/src/port.rs +++ b/3/coursework/src/database/src/port.rs @@ -2,20 +2,21 @@ pub type Result = std::result::Result>; #[allow(async_fn_in_trait)] pub trait CRUD { - type Create; - type Read; + type New; + type Unique; type Update; - type Delete; type Existing; - type Id; - async fn create(connection: &mut C, data: Self::Create) -> Result; - async fn read(connection: &C, data: Self::Read) -> Result>; - async fn update(connection: &mut C, id: Self::Id, data: Self::Update) -> Result; - async fn delete(connection: &mut C, data: Self::Delete) -> Result; + async fn create(connection: &mut C, data: Self::New) -> Result; + async fn read(connection: &C, data: Self::Unique) -> Result>; + async fn update( + connection: &mut C, + existing: &mut Self::Existing, + data: Self::Update, + ) -> Result; + async fn delete(connection: &mut C, data: Self::Unique) -> Result; } -pub mod user; pub mod base; -// pub mod package; -// pub mod session; +pub mod package; +pub mod user; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs index f241124..d3fe2bc 100644 --- a/3/coursework/src/database/src/port/base.rs +++ b/3/coursework/src/database/src/port/base.rs @@ -6,45 +6,21 @@ use garde::{Valid, Validate}; #[allow(async_fn_in_trait)] pub trait BaseRepository: - super::CRUD< - C, - Create = New, - Read = Id, - Update = Field, - Delete = Id, - Existing = Base, - Id = Id, - > + CRUD { - async fn update_base(connection: &mut C, base: &mut Base, data: Self::Update) -> Result { - Self::update(connection, base.id, data.clone()).await?; - match data { - Field::Name(valid) => base.name = valid.into_inner(), - Field::Description(valid) => base.description = valid.into_inner(), - Field::CreatedAt(date_time) => base.created_at = date_time, - Field::UpdatedAt(date_time) => base.updated_at = date_time, - } - Ok(()) - } } -#[derive(Deref, Into, Clone, Copy)] -pub struct Id(pub(crate) u64); +// #[derive(Deref, Into, Clone, Copy)] +// pub struct Id(pub(crate) u64); -#[derive(Validate, Deref, From, Clone)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); +pub struct Name(#[garde(length(chars, max = 127))] pub String); -#[derive(Validate, Deref, From, Clone)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Description(#[garde(length(max = 510))] pub Option); +pub struct Description(#[garde(length(chars, max = 510))] pub Option); -pub struct New { - pub name: Name, - pub description: Description, -} - -#[derive(Clone)] pub enum Field { Name(Valid), Description(Valid), @@ -52,28 +28,33 @@ pub enum Field { UpdatedAt(DateTime), } +pub struct New { + pub name: Valid, + pub description: Valid, +} + pub struct Base { - pub(crate) id: Id, - pub(crate) name: Name, - pub(crate) description: Description, + pub(crate) id: u64, + pub(crate) name: String, + pub(crate) description: Option, pub(crate) created_at: DateTime, pub(crate) updated_at: DateTime, } impl Base { - pub fn id(&self) -> Id { + pub const fn id(&self) -> u64 { self.id } - pub fn name(&self) -> &Name { + pub const fn name(&self) -> &String { &self.name } - pub fn description(&self) -> &Description { - &self.description + pub const fn description(&self) -> Option<&String> { + self.description.as_ref() } - pub fn created_at(&self) -> DateTime { + pub const fn created_at(&self) -> DateTime { self.created_at } - pub fn updated_at(&self) -> DateTime { + pub const fn updated_at(&self) -> DateTime { self.updated_at } } diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs index 72de830..cdb50b7 100644 --- a/3/coursework/src/database/src/port/package.rs +++ b/3/coursework/src/database/src/port/package.rs @@ -1,168 +1,94 @@ -use super::{Result, package_base::Base}; +pub use super::{CRUD, Result, base::Base}; +pub use chrono::{DateTime, Utc}; use derive_more::{Deref, From, Into}; use garde::{Valid, Validate}; -use sqlx::{Executor, MySql}; - -// pub enum GetBy #[allow(async_fn_in_trait)] -pub trait PackageRepository { - async fn get_by_id(connection: &C, id: Id) -> Result>; - async fn get_by_name(connection: &C, name: &Valid) -> Result>; - - async fn change_name(connection: &mut C, package: &mut Package, name: Valid) -> Result; - async fn change_base(connection: &mut C, package: &mut Package, base: &Base) -> Result; - async fn change_version(connection: &mut C, package: &mut Package, version: Valid) -> Result; - - async fn create(connection: &mut C, data: Valid) -> Result; +pub trait PackageRepository: + CRUD +{ } -#[derive(Deref, Into, Clone, Copy)] -pub struct Id(u32); - -pub type BaseId = super::package_base::Id; - -#[derive(Validate, Deref)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); +pub struct Name(#[garde(length(chars, max = 127))] pub String); -#[derive(Validate, Deref)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Version(#[garde(alphanumeric, length(min = 1, max = 127))] pub String); +pub struct Version(#[garde(length(chars, max = 127))] pub String); -#[derive(Validate, Deref)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Description(#[garde(ascii, length(max = 255))] pub Option); +pub struct Description(#[garde(length(chars, max = 255))] pub Option); -#[derive(Validate)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct URL(#[garde(url, length(max = 510))] pub Option); +pub struct URL(#[garde(length(chars, max = 510))] pub Option); -#[derive(Validate)] -pub struct PackageData { - #[garde(dive)] - pub name: Name, - #[garde(dive)] - pub description: Description, +pub enum Unique { + Id(u64), + Name(Valid), +} + +pub enum Field { + PackageBase(Base), + Name(Valid), + Version(Valid), + Description(Valid), + URL(Valid), + FlaggedAt(Option>), + CreatedAt(DateTime), + UpdatedAt(DateTime), +} + +pub struct New { + pub package_base: Base, + pub name: Valid, + pub version: Valid, + pub description: Valid, + pub url: Valid, + pub flagged_at: Option>, } -#[derive(Deref)] pub struct Package { - id: Id, - #[deref] - data: PackageData, + pub(crate) id: u64, + pub(crate) package_base: u64, + pub(crate) name: String, + pub(crate) version: String, + pub(crate) description: Option, + pub(crate) url: Option, + pub(crate) flagged_at: Option>, + pub(crate) created_at: DateTime, + pub(crate) updated_at: DateTime, } + impl Package { - pub const fn id(&self) -> Id { + pub const fn id(&self) -> u64 { self.id } + pub const fn package_base(&self) -> u64 { + self.package_base + } + pub const fn name(&self) -> &String { + &self.name + } + pub const fn version(&self) -> &String { + &self.version + } + pub const fn description(&self) -> Option<&String> { + self.description.as_ref() + } + pub const fn url(&self) -> Option<&String> { + self.url.as_ref() + } + pub const fn flagged_at(&self) -> Option> { + self.flagged_at + } + pub const fn created_at(&self) -> DateTime { + self.created_at + } + pub const fn updated_at(&self) -> DateTime { + self.updated_at + } } - -// pub struct UserAdapter; -// -// struct QueryUser { -// id: u32, -// name: String, -// email: String, -// password: String, -// } -// impl From for Package { -// fn from(value: QueryUser) -> Self { -// Self { -// id: Id(value.id), -// data: PackageData { -// name: Name(value.name), -// description: Description(value.email), -// }, -// } -// } -// } -// -// impl PackageRepository for UserAdapter -// where -// for<'a> &'a E: Executor<'a, Database = MySql>, -// { -// async fn get_by_id(connection: &E, id: Id) -> Result> { -// Ok(sqlx::query_as!( -// QueryUser, -// "SELECT id, name, email, password FROM Users WHERE id = ?", -// id.0 -// ) -// .fetch_optional(connection) -// .await? -// .map(Into::into)) -// } -// async fn get_by_name(connection: &E, name: &Valid) -> Result> { -// Ok(sqlx::query_as!( -// QueryUser, -// "SELECT id, name, email, password FROM Users WHERE name = ?", -// name.0 -// ) -// .fetch_optional(connection) -// .await? -// .map(Into::into)) -// } -// async fn get_by_email(connection: &E, email: &Valid) -> Result> { -// Ok(sqlx::query_as!( -// QueryUser, -// "SELECT id, name, email, password FROM Users WHERE email = ?", -// email.0 -// ) -// .fetch_optional(connection) -// .await? -// .map(Into::into)) -// } -// -// async fn change_name(connection: &mut E, user: &mut Package, name: Valid) -> Result { -// sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) -// .execute(&*connection) -// .await?; -// Ok(()) -// } -// async fn change_email( -// connection: &mut E, -// user: &mut Package, -// email: Valid, -// ) -> Result { -// sqlx::query!( -// "UPDATE Users SET email = ? WHERE id = ?", -// email.0, -// user.id.0 -// ) -// .execute(&*connection) -// .await?; -// Ok(()) -// } -// async fn change_password( -// connection: &mut E, -// user: &mut Package, -// password: Valid, -// ) -> Result { -// sqlx::query!( -// "UPDATE Users SET password = ? WHERE id = ?", -// password.0, -// user.id.0 -// ) -// .execute(&*connection) -// .await?; -// Ok(()) -// } -// -// async fn create(connection: &mut E, data: Valid) -> Result { -// let id = sqlx::query!( -// "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", -// data.name.0, -// data.description.0, -// data.password.0 -// ) -// .execute(&*connection) -// .await? -// .last_insert_id() as u32; -// -// Ok(Package { -// id: Id(id), -// data: data.into_inner(), -// }) -// } -// } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs index afa448b..b0a3933 100644 --- a/3/coursework/src/database/src/port/user.rs +++ b/3/coursework/src/database/src/port/user.rs @@ -6,61 +6,28 @@ use garde::{Valid, Validate}; #[allow(async_fn_in_trait)] pub trait UserRepository: - super::CRUD< - C, - Create = New, - Read = Unique, - Update = Field, - Delete = Unique, - Existing = User, - Id = Id, - > + CRUD { - async fn update_user(connection: &mut C, user: &mut User, data: Self::Update) -> Result { - Self::update(connection, user.id, data.clone()).await?; - match data { - Field::Name(valid) => user.name = valid.into_inner(), - Field::Email(valid) => user.email = valid.into_inner(), - Field::Password(valid) => user.password = valid.into_inner(), - Field::LastUsed(date_time) => user.last_used = date_time, - Field::CreatedAt(date_time) => user.created_at = date_time, - Field::UpdatedAt(date_time) => user.updated_at = date_time, - } - Ok(()) - } } -#[derive(Deref, Into, Clone, Copy)] -pub struct Id(pub(crate) u64); - -// TODO: is this the right layer for requirements (email) validatoin? - -#[derive(Validate, Deref, From, Clone)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Name(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); +pub struct Name(#[garde(length(chars, max = 31))] pub String); -#[derive(Validate, Deref, From, Clone)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Email(#[garde(email, length(max = 255))] pub String); +pub struct Email(#[garde(length(chars, max = 255))] pub String); -#[derive(Validate, Deref, From, Clone)] +#[derive(Validate, Deref, From, Into)] #[garde(transparent)] -pub struct Password(#[garde(ascii, length(max = 255))] pub String); - -pub struct New { - pub name: Valid, - pub email: Valid, - pub password: Valid, - pub last_used: Option>, -} +pub struct Password(#[garde(length(chars, max = 255))] pub String); pub enum Unique { - Id(Id), + Id(u64), Name(Valid), Email(Valid), } -#[derive(Clone)] pub enum Field { Name(Valid), Email(Valid), @@ -70,27 +37,34 @@ pub enum Field { UpdatedAt(DateTime), } +pub struct New { + pub name: Valid, + pub email: Valid, + pub password: Valid, + pub last_used: Option>, +} + pub struct User { - pub(crate) id: Id, - pub(crate) name: Name, - pub(crate) email: Email, - pub(crate) password: Password, + pub(crate) id: u64, + pub(crate) name: String, + pub(crate) email: String, + pub(crate) password: String, pub(crate) last_used: Option>, pub(crate) created_at: DateTime, pub(crate) updated_at: DateTime, } impl User { - pub const fn id(&self) -> Id { + pub const fn id(&self) -> u64 { self.id } - pub const fn name(&self) -> &Name { + pub const fn name(&self) -> &String { &self.name } - pub const fn email(&self) -> &Email { + pub const fn email(&self) -> &String { &self.email } - pub const fn password(&self) -> &Password { + pub const fn password(&self) -> &String { &self.password } pub const fn last_used(&self) -> Option> { From ee794811c3fe6e8f10b80da0f33b6d193dec2efa Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Fri, 31 Jan 2025 20:34:24 +0200 Subject: [PATCH 04/13] Simplify database field verification --- 3/coursework/src/Cargo.lock | 57 +--------- ...85df75ae8e3c95a1a7c6c21be7b5624a82ae1.json | 12 ++ ...4472929e6b2618ab13eb90e772ad9bd9c1984.json | 12 ++ ...efca62c247c4b2265a9667c4b33885126c771.json | 12 ++ ...4513493670e3f2ae017d87a2f0c3eca045f60.json | 84 ++++++++++++++ ...190ac327ae79f6e555def8fec89fcc75fb015.json | 12 ++ ...0a468da889a3455c47325938b819ab41ef4c8.json | 12 ++ ...63594bb8273e66c053934ec20d5fc3db9d41e.json | 12 ++ ...24b553d8ac922da251d9e8b8f4e897bab46b0.json | 84 ++++++++++++++ ...4a9cd92aaea3ea58ef8702903983cfc32ab47.json | 104 ++++++++++++++++++ ...a79a09f3e3d8cfd42d3946bd1fac93838b913.json | 12 ++ ...5f70903a38ed314235712c28ac5e14d9ac20f.json | 12 ++ ...8b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json | 64 +++++++++++ ...e921f719ed9466c13713fda8736c540d0fa78.json | 12 ++ ...11134ec0f43504d3a820698282848fd67dbad.json | 12 ++ ...49849d29af3d6345d0e02474abf4d8c78b89d.json | 12 ++ ...33588c0679404a68a8041f414553226abeeb2.json | 84 ++++++++++++++ ...4db874788ed297fe2d95275592becb186f942.json | 12 ++ ...e6efb16919aff7172189c81240cb12462ae58.json | 104 ++++++++++++++++++ ...8d28035747b66e57260bc9cd4634a71a037a6.json | 12 ++ ...6010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json | 12 ++ ...6b0b9ae811ba6bff0617871e76de3ef0ff662.json | 12 ++ ...ef4fcd78f8571cfe2347c147124763bd17491.json | 12 ++ ...a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json | 12 ++ ...8f9b092d74c2298ac126f7edbb7d59c536910.json | 12 ++ ...b7aa98b84baf911e7f779641c1dc514c676cd.json | 12 ++ ...dd8dccd1a4e6a763963590c904c25abf33137.json | 12 ++ ...1546f10e8213d36b6b3cd25016f829e9d731b.json | 12 ++ ...a562a3c4a4569032e8636d664043b5dc96661.json | 12 ++ ...90e92bccd89d1ff90131719c40626088dabd1.json | 12 ++ ...8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json | 12 ++ ...8971cb804a1ab1edcdae8bf009ac39059c2bb.json | 12 ++ ...b7ee842c26c44da32ae9bbbc06c466a908ccf.json | 12 ++ ...4a364988f5329356282f2ae0098dbfcaec671.json | 12 ++ 3/coursework/src/database/Cargo.toml | 7 +- .../src/database/src/adapter/mysql/base.rs | 16 +-- .../src/database/src/adapter/mysql/package.rs | 46 ++++---- .../src/database/src/adapter/mysql/user.rs | 32 +++--- 3/coursework/src/database/src/lib.rs | 27 ++--- 3/coursework/src/database/src/port.rs | 2 + 3/coursework/src/database/src/port/base.rs | 44 ++++++-- 3/coursework/src/database/src/port/package.rs | 87 +++++++++++---- 3/coursework/src/database/src/port/user.rs | 67 +++++++---- 43 files changed, 1062 insertions(+), 171 deletions(-) create mode 100644 3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json create mode 100644 3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json create mode 100644 3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json create mode 100644 3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json create mode 100644 3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json create mode 100644 3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json create mode 100644 3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json create mode 100644 3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json create mode 100644 3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json create mode 100644 3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json create mode 100644 3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json create mode 100644 3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json create mode 100644 3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json create mode 100644 3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json create mode 100644 3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json create mode 100644 3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json create mode 100644 3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json create mode 100644 3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json create mode 100644 3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json create mode 100644 3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json create mode 100644 3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json create mode 100644 3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json create mode 100644 3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json create mode 100644 3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json create mode 100644 3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json create mode 100644 3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json create mode 100644 3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json create mode 100644 3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json create mode 100644 3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json create mode 100644 3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json create mode 100644 3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json create mode 100644 3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json create mode 100644 3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 22d0b7d..9a38ac1 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -57,15 +57,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "aliasable" version = "0.1.3" @@ -332,6 +323,7 @@ version = "0.1.0" dependencies = [ "argon2", "database", + "derive_more", "garde", "thiserror 2.0.11", ] @@ -915,9 +907,7 @@ version = "0.1.0" dependencies = [ "chrono", "derive_more", - "garde", "sqlx", - "thiserror 2.0.11", ] [[package]] @@ -1430,23 +1420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a989bd2fd12136080f7825ff410d9239ce84a2a639487fc9d924ee42e2fb84f" dependencies = [ "compact_str", - "garde_derive", - "once_cell", - "regex", "smallvec", - "url", -] - -[[package]] -name = "garde_derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7f0545bbbba0a37d4d445890fa5759814e0716f02417b39f6fab292193df68" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.96", ] [[package]] @@ -3239,35 +3213,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "renderdoc-sys" version = "1.1.0" diff --git a/3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json b/3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json new file mode 100644 index 0000000..6635f37 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO PackageBases (name, description, created_at, updated_at) VALUES (?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1" +} diff --git a/3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json b/3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json new file mode 100644 index 0000000..5e754bf --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET created_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984" +} diff --git a/3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json b/3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json new file mode 100644 index 0000000..97263b5 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE PackageBases SET name = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771" +} diff --git a/3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json b/3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json new file mode 100644 index 0000000..0f4639e --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json @@ -0,0 +1,84 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM Users WHERE email = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 124 + } + }, + { + "ordinal": 2, + "name": "email", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 3, + "name": "password", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 4, + "name": "last_used", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 6, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60" +} diff --git a/3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json b/3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json new file mode 100644 index 0000000..ae86705 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET version = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015" +} diff --git a/3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json b/3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json new file mode 100644 index 0000000..2e79b5c --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM PackageBases WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8" +} diff --git a/3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json b/3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json new file mode 100644 index 0000000..6a26eca --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM Users WHERE name = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e" +} diff --git a/3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json b/3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json new file mode 100644 index 0000000..688e561 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json @@ -0,0 +1,84 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM Users WHERE name = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 124 + } + }, + { + "ordinal": 2, + "name": "email", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 3, + "name": "password", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 4, + "name": "last_used", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 6, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0" +} diff --git a/3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json b/3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json new file mode 100644 index 0000000..64fcfb8 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json @@ -0,0 +1,104 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM Packages WHERE id = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "package_base", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE", + "max_size": 10 + } + }, + { + "ordinal": 2, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 508 + } + }, + { + "ordinal": 3, + "name": "version", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "max_size": 508 + } + }, + { + "ordinal": 4, + "name": "description", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 1020 + } + }, + { + "ordinal": 5, + "name": "url", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 2040 + } + }, + { + "ordinal": 6, + "name": "flagged_at", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + }, + { + "ordinal": 7, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 8, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47" +} diff --git a/3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json b/3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json new file mode 100644 index 0000000..27f3c2e --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET updated_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913" +} diff --git a/3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json b/3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json new file mode 100644 index 0000000..db397d0 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE PackageBases SET description = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f" +} diff --git a/3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json b/3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json new file mode 100644 index 0000000..85c33af --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json @@ -0,0 +1,64 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM PackageBases WHERE id = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 508 + } + }, + { + "ordinal": 2, + "name": "description", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 2040 + } + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 4, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true, + false, + false + ] + }, + "hash": "839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc" +} diff --git a/3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json b/3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json new file mode 100644 index 0000000..0a0d0ad --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET updated_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78" +} diff --git a/3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json b/3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json new file mode 100644 index 0000000..8b24831 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET password = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad" +} diff --git a/3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json b/3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json new file mode 100644 index 0000000..ecb0a98 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET flagged_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d" +} diff --git a/3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json b/3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json new file mode 100644 index 0000000..514a1f9 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json @@ -0,0 +1,84 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM Users WHERE id = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 124 + } + }, + { + "ordinal": 2, + "name": "email", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 3, + "name": "password", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "max_size": 1020 + } + }, + { + "ordinal": 4, + "name": "last_used", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 6, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2" +} diff --git a/3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json b/3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json new file mode 100644 index 0000000..a0b5b1c --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE PackageBases SET created_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942" +} diff --git a/3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json b/3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json new file mode 100644 index 0000000..023fc05 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json @@ -0,0 +1,104 @@ +{ + "db_name": "MySQL", + "query": "SELECT * FROM Packages WHERE name = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "package_base", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE", + "max_size": 10 + } + }, + { + "ordinal": 2, + "name": "name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE", + "max_size": 508 + } + }, + { + "ordinal": 3, + "name": "version", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | NO_DEFAULT_VALUE", + "max_size": 508 + } + }, + { + "ordinal": 4, + "name": "description", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 1020 + } + }, + { + "ordinal": 5, + "name": "url", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 2040 + } + }, + { + "ordinal": 6, + "name": "flagged_at", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + }, + { + "ordinal": 7, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP", + "max_size": 19 + } + }, + { + "ordinal": 8, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58" +} diff --git a/3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json b/3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json new file mode 100644 index 0000000..083bb37 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET email = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6" +} diff --git a/3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json b/3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json new file mode 100644 index 0000000..7c34a5b --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM Users WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce" +} diff --git a/3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json b/3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json new file mode 100644 index 0000000..db15194 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET url = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662" +} diff --git a/3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json b/3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json new file mode 100644 index 0000000..3d9e1ec --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET description = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491" +} diff --git a/3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json b/3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json new file mode 100644 index 0000000..5e9d5c5 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM Users WHERE email = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06" +} diff --git a/3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json b/3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json new file mode 100644 index 0000000..a432718 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET package_base = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910" +} diff --git a/3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json b/3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json new file mode 100644 index 0000000..efa1af3 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET last_used = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd" +} diff --git a/3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json b/3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json new file mode 100644 index 0000000..d27cc6d --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET created_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137" +} diff --git a/3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json b/3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json new file mode 100644 index 0000000..60dd430 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE PackageBases SET updated_at = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b" +} diff --git a/3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json b/3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json new file mode 100644 index 0000000..5a21f41 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM Packages WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661" +} diff --git a/3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json b/3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json new file mode 100644 index 0000000..6108db8 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO Users (name, email, password, last_used, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1" +} diff --git a/3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json b/3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json new file mode 100644 index 0000000..5c3f63a --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO Packages (package_base, name, version, description, url, flagged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 8 + }, + "nullable": [] + }, + "hash": "dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634" +} diff --git a/3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json b/3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json new file mode 100644 index 0000000..9841c3d --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET name = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb" +} diff --git a/3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json b/3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json new file mode 100644 index 0000000..722ae18 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "DELETE FROM Packages WHERE name = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf" +} diff --git a/3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json b/3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json new file mode 100644 index 0000000..e818217 --- /dev/null +++ b/3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Users SET name = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671" +} diff --git a/3/coursework/src/database/Cargo.toml b/3/coursework/src/database/Cargo.toml index 69edf73..0ed9ab6 100644 --- a/3/coursework/src/database/Cargo.toml +++ b/3/coursework/src/database/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -thiserror = "2.0.11" -derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "from", "into"] } -garde = { version = "0.22.0", features = ["email", "url", "derive"] } +derive_more = { version = "1.0.0", features = ["deref", "into"] } chrono = { version = "0.4.39", default-features = false, features = [ "std", @@ -18,3 +16,6 @@ sqlx = { version = "0.8.3", default-features = false, features = [ "chrono", "runtime-tokio", ] } + +# thiserror = "2.0.11" +# garde = { version = "0.22.0", features = ["email", "url", "derive"] } diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/database/src/adapter/mysql/base.rs index 347013e..9eaa45d 100644 --- a/3/coursework/src/database/src/adapter/mysql/base.rs +++ b/3/coursework/src/database/src/adapter/mysql/base.rs @@ -18,8 +18,8 @@ where let created_at = Utc::now(); let id = sqlx::query!( "INSERT INTO PackageBases (name, description, created_at, updated_at) VALUES (?, ?, ?, ?)", - data.name.0, - data.description.0, + data.name.as_str(), + data.description.as_ref(), created_at, created_at, ) .execute(&*connection) @@ -28,8 +28,8 @@ where Ok(Self::Existing { id, - name: data.name.into_inner().0, - description: data.description.into_inner().0, + name: data.name.into(), + description: data.description.into(), created_at, updated_at: created_at, }) @@ -52,14 +52,14 @@ where Field::Name(name) => { sqlx::query!( "UPDATE PackageBases SET name = ? WHERE id = ?", - name.0, + name.as_str(), existing.id ) } Field::Description(description) => { sqlx::query!( "UPDATE PackageBases SET description = ? WHERE id = ?", - description.0, + description.as_ref(), existing.id ) } @@ -78,8 +78,8 @@ where .await?; match data { - Field::Name(valid) => existing.name = valid.into_inner().0, - Field::Description(valid) => existing.description = valid.into_inner().0, + Field::Name(s) => existing.name = s.into(), + Field::Description(o) => existing.description = o.into(), Field::CreatedAt(date_time) => existing.created_at = date_time, Field::UpdatedAt(date_time) => existing.updated_at = date_time, } diff --git a/3/coursework/src/database/src/adapter/mysql/package.rs b/3/coursework/src/database/src/adapter/mysql/package.rs index 7cb483d..8ea6aea 100644 --- a/3/coursework/src/database/src/adapter/mysql/package.rs +++ b/3/coursework/src/database/src/adapter/mysql/package.rs @@ -21,10 +21,10 @@ where (package_base, name, version, description, url, flagged_at, created_at, updated_at) \ VALUES (?, ?, ?, ?, ?, ?, ?, ?)", data.package_base.id, - data.name.0, - data.version.0, - data.description.0, - data.url.0, + data.name.as_str(), + data.version.as_str(), + data.description.as_ref(), + data.url.as_ref(), data.flagged_at, created_at, created_at, @@ -36,10 +36,10 @@ where Ok(Self::Existing { id, package_base: data.package_base.id, - name: data.name.into_inner().0, - version: data.version.into_inner().0, - description: data.description.into_inner().0, - url: data.url.into_inner().0, + name: data.name.into(), + version: data.version.into(), + description: data.description.into(), + url: data.url.into(), flagged_at: data.flagged_at, created_at, updated_at: created_at, @@ -54,9 +54,13 @@ where .await } Unique::Name(name) => { - sqlx::query_as!(Package, "SELECT * FROM Packages WHERE name = ?", name.0) - .fetch_optional(connection) - .await + sqlx::query_as!( + Package, + "SELECT * FROM Packages WHERE name = ?", + name.as_str() + ) + .fetch_optional(connection) + .await } }?) } @@ -70,7 +74,7 @@ where Field::Name(name) => { sqlx::query!( "UPDATE Packages SET name = ? WHERE id = ?", - name.0, + name.as_str(), existing.id ) } @@ -84,21 +88,21 @@ where Field::Version(version) => { sqlx::query!( "UPDATE Packages SET version = ? WHERE id = ?", - version.0, + version.as_str(), existing.id ) } Field::Description(description) => { sqlx::query!( "UPDATE Packages SET description = ? WHERE id = ?", - description.0, + description.as_ref(), existing.id ) } Field::URL(url) => { sqlx::query!( "UPDATE Packages SET url = ? WHERE id = ?", - url.0, + url.as_ref(), existing.id ) } @@ -122,11 +126,11 @@ where .await?; match data { - Field::Name(valid) => existing.name = valid.into_inner().0, - Field::PackageBase(base) => existing.package_base = base.id, - Field::Version(valid) => existing.version = valid.into_inner().0, - Field::Description(valid) => existing.description = valid.into_inner().0, - Field::URL(valid) => existing.url = valid.into_inner().0, + Field::Name(s) => existing.name = s.into(), + Field::PackageBase(s) => existing.package_base = s.id, + Field::Version(s) => existing.version = s.into(), + Field::Description(o) => existing.description = o.into(), + Field::URL(o) => existing.url = o.into(), Field::FlaggedAt(date_time) => existing.flagged_at = date_time, Field::CreatedAt(date_time) => existing.created_at = date_time, Field::UpdatedAt(date_time) => existing.updated_at = date_time, @@ -139,7 +143,7 @@ where match data { Unique::Id(id) => sqlx::query!("DELETE FROM Packages WHERE id = ?", id), Unique::Name(name) => { - sqlx::query!("DELETE FROM Packages WHERE name = ?", name.0) + sqlx::query!("DELETE FROM Packages WHERE name = ?", name.as_str()) } } .execute(&*connection) diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/database/src/adapter/mysql/user.rs index 60d0df3..e647e47 100644 --- a/3/coursework/src/database/src/adapter/mysql/user.rs +++ b/3/coursework/src/database/src/adapter/mysql/user.rs @@ -18,9 +18,9 @@ where let created_at = Utc::now(); let id = sqlx::query!( "INSERT INTO Users (name, email, password, last_used, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", - data.name.0, - data.email.0, - data.password.0, + data.name.as_str(), + data.email.as_str(), + data.password.as_str(), data.last_used, created_at, created_at, @@ -31,9 +31,9 @@ where Ok(Self::Existing { id, - name: data.name.into_inner().0, - email: data.email.into_inner().0, - password: data.password.into_inner().0, + name: data.name.into(), + email: data.email.into(), + password: data.password.into(), last_used: data.last_used, created_at, updated_at: created_at, @@ -48,12 +48,12 @@ where .await } Unique::Name(name) => { - sqlx::query_as!(User, "SELECT * FROM Users WHERE name = ?", name.0) + sqlx::query_as!(User, "SELECT * FROM Users WHERE name = ?", name.as_str()) .fetch_optional(connection) .await } Unique::Email(email) => { - sqlx::query_as!(User, "SELECT * FROM Users WHERE email = ?", email.0) + sqlx::query_as!(User, "SELECT * FROM Users WHERE email = ?", email.as_str()) .fetch_optional(connection) .await } @@ -69,21 +69,21 @@ where Field::Name(name) => { sqlx::query!( "UPDATE Users SET name = ? WHERE id = ?", - name.0, + name.as_str(), existing.id ) } Field::Email(email) => { sqlx::query!( "UPDATE Users SET email = ? WHERE id = ?", - email.0, + email.as_str(), existing.id ) } Field::Password(password) => { sqlx::query!( "UPDATE Users SET password = ? WHERE id = ?", - password.0, + password.as_str(), existing.id ) } @@ -109,9 +109,9 @@ where .await?; match data { - Field::Name(valid) => existing.name = valid.into_inner().0, - Field::Email(valid) => existing.email = valid.into_inner().0, - Field::Password(valid) => existing.password = valid.into_inner().0, + Field::Name(valid) => existing.name = valid.into(), + Field::Email(valid) => existing.email = valid.into(), + Field::Password(valid) => existing.password = valid.into(), Field::LastUsed(date_time) => existing.last_used = date_time, Field::CreatedAt(date_time) => existing.created_at = date_time, Field::UpdatedAt(date_time) => existing.updated_at = date_time, @@ -124,10 +124,10 @@ where match data { Unique::Id(id) => sqlx::query!("DELETE FROM Users WHERE id = ?", id), Unique::Name(name) => { - sqlx::query!("DELETE FROM Users WHERE name = ?", name.0) + sqlx::query!("DELETE FROM Users WHERE name = ?", name.as_str()) } Unique::Email(email) => { - sqlx::query!("DELETE FROM Users WHERE email = ?", email.0) + sqlx::query!("DELETE FROM Users WHERE email = ?", email.as_str()) } } .execute(&*connection) diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs index 86d563e..0108ec5 100644 --- a/3/coursework/src/database/src/lib.rs +++ b/3/coursework/src/database/src/lib.rs @@ -1,17 +1,18 @@ -use garde::{Report, Unvalidated, Valid, Validate}; - -pub trait IntoValid: Validate { - fn into_valid(self) -> Result, Report> - where - Self: Sized, - ::Context: Default, - { - Unvalidated::new(self).validate() - } -} -impl IntoValid for T {} - pub mod adapter; pub mod atomic; pub mod connect; pub mod port; + +// use garde::{Report, Unvalidated, Valid, Validate}; + +// pub trait IntoValid: Validate { +// fn into_valid(self) -> Result, Report> +// where +// Self: Sized, +// ::Context: Default, +// { +// Unvalidated::new(self).validate() +// } +// } +// impl IntoValid for T {} + diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs index d2e13cf..89c114e 100644 --- a/3/coursework/src/database/src/port.rs +++ b/3/coursework/src/database/src/port.rs @@ -17,6 +17,8 @@ pub trait CRUD { async fn delete(connection: &mut C, data: Self::Unique) -> Result; } +const TOO_LONG: &str = "too long"; + pub mod base; pub mod package; pub mod user; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs index d3fe2bc..bdc18ab 100644 --- a/3/coursework/src/database/src/port/base.rs +++ b/3/coursework/src/database/src/port/base.rs @@ -1,8 +1,7 @@ pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; -use derive_more::{Deref, From, Into}; -use garde::{Valid, Validate}; +use derive_more::{Deref, Into}; #[allow(async_fn_in_trait)] pub trait BaseRepository: @@ -13,24 +12,45 @@ pub trait BaseRepository: // #[derive(Deref, Into, Clone, Copy)] // pub struct Id(pub(crate) u64); -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Name(#[garde(length(chars, max = 127))] pub String); +#[derive(Clone, Deref, Into)] +pub struct Name(String); +impl TryFrom for Name { + type Error = &'static str; -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Description(#[garde(length(chars, max = 510))] pub Option); + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 127 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Description(Option); +impl TryFrom> for Description { + type Error = &'static str; + + fn try_from(value: Option) -> std::result::Result { + if let Some(x) = &value { + if x.chars().count() > 510 { + return Err(super::TOO_LONG); + } + } + Ok(Self(value)) + } +} pub enum Field { - Name(Valid), - Description(Valid), + Name(Name), + Description(Description), CreatedAt(DateTime), UpdatedAt(DateTime), } pub struct New { - pub name: Valid, - pub description: Valid, + pub name: Name, + pub description: Description, } pub struct Base { diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs index cdb50b7..f37d852 100644 --- a/3/coursework/src/database/src/port/package.rs +++ b/3/coursework/src/database/src/port/package.rs @@ -1,8 +1,7 @@ pub use super::{CRUD, Result, base::Base}; pub use chrono::{DateTime, Utc}; -use derive_more::{Deref, From, Into}; -use garde::{Valid, Validate}; +use derive_more::{Deref, Into}; #[allow(async_fn_in_trait)] pub trait PackageRepository: @@ -10,33 +9,75 @@ pub trait PackageRepository: { } -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Name(#[garde(length(chars, max = 127))] pub String); +#[derive(Clone, Deref, Into)] +pub struct Name(String); +impl TryFrom for Name { + type Error = &'static str; -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Version(#[garde(length(chars, max = 127))] pub String); + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 127 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Description(#[garde(length(chars, max = 255))] pub Option); +#[derive(Clone, Deref, Into)] +pub struct Version(String); +impl TryFrom for Version { + type Error = &'static str; -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct URL(#[garde(length(chars, max = 510))] pub Option); + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 127 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Description(Option); +impl TryFrom> for Description { + type Error = &'static str; + + fn try_from(value: Option) -> std::result::Result { + if let Some(x) = &value { + if x.chars().count() > 255 { + return Err(super::TOO_LONG); + } + } + Ok(Self(value)) + } +} + +#[derive(Clone, Deref, Into)] +pub struct URL(Option); +impl TryFrom> for URL { + type Error = &'static str; + + fn try_from(value: Option) -> std::result::Result { + if let Some(x) = &value { + if x.chars().count() > 510 { + return Err(super::TOO_LONG); + } + } + Ok(Self(value)) + } +} pub enum Unique { Id(u64), - Name(Valid), + Name(Name), } pub enum Field { PackageBase(Base), - Name(Valid), - Version(Valid), - Description(Valid), - URL(Valid), + Name(Name), + Version(Version), + Description(Description), + URL(URL), FlaggedAt(Option>), CreatedAt(DateTime), UpdatedAt(DateTime), @@ -44,10 +85,10 @@ pub enum Field { pub struct New { pub package_base: Base, - pub name: Valid, - pub version: Valid, - pub description: Valid, - pub url: Valid, + pub name: Name, + pub version: Version, + pub description: Description, + pub url: URL, pub flagged_at: Option>, } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs index b0a3933..64f096d 100644 --- a/3/coursework/src/database/src/port/user.rs +++ b/3/coursework/src/database/src/port/user.rs @@ -1,8 +1,7 @@ pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; -use derive_more::{Deref, From, Into}; -use garde::{Valid, Validate}; +use derive_more::{Deref, Into}; #[allow(async_fn_in_trait)] pub trait UserRepository: @@ -10,37 +9,67 @@ pub trait UserRepository: { } -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Name(#[garde(length(chars, max = 31))] pub String); +#[derive(Clone, Deref, Into)] +pub struct Name(String); +impl TryFrom for Name { + type Error = &'static str; -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Email(#[garde(length(chars, max = 255))] pub String); + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 31 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} -#[derive(Validate, Deref, From, Into)] -#[garde(transparent)] -pub struct Password(#[garde(length(chars, max = 255))] pub String); +#[derive(Clone, Deref, Into)] +pub struct Email(String); +impl TryFrom for Email { + type Error = &'static str; + + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 255 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Password(String); +impl TryFrom for Password { + type Error = &'static str; + + fn try_from(value: String) -> std::result::Result { + if value.chars().count() > 255 { + Err(super::TOO_LONG) + } else { + Ok(Self(value)) + } + } +} pub enum Unique { Id(u64), - Name(Valid), - Email(Valid), + Name(Name), + Email(Email), } pub enum Field { - Name(Valid), - Email(Valid), - Password(Valid), + Name(Name), + Email(Email), + Password(Password), LastUsed(Option>), CreatedAt(DateTime), UpdatedAt(DateTime), } pub struct New { - pub name: Valid, - pub email: Valid, - pub password: Valid, + pub name: Name, + pub email: Email, + pub password: Password, pub last_used: Option>, } From 517a81aa50a17c62bb90e6ef8ed4891db261bbb5 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Sat, 1 Feb 2025 11:33:57 +0200 Subject: [PATCH 05/13] Authentication service --- 3/coursework/src/Cargo.lock | 54 +++++ .../src/app/authentication/Cargo.toml | 14 ++ .../src/app/authentication/src/lib.rs | 4 + .../src/app/authentication/src/repository.rs | 86 +++++++ .../src/app/authentication/src/service.rs | 212 ++++++++++++++++++ 3/coursework/src/database/src/port.rs | 31 ++- 3/coursework/src/database/src/port/base.rs | 26 ++- 3/coursework/src/database/src/port/package.rs | 51 +++-- 3/coursework/src/database/src/port/user.rs | 40 ++-- 9 files changed, 462 insertions(+), 56 deletions(-) create mode 100644 3/coursework/src/app/authentication/Cargo.toml create mode 100644 3/coursework/src/app/authentication/src/lib.rs create mode 100644 3/coursework/src/app/authentication/src/repository.rs create mode 100644 3/coursework/src/app/authentication/src/service.rs diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 9a38ac1..679dee7 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -57,6 +57,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -1420,7 +1429,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a989bd2fd12136080f7825ff410d9239ce84a2a639487fc9d924ee42e2fb84f" dependencies = [ "compact_str", + "garde_derive", + "once_cell", + "regex", "smallvec", + "url", +] + +[[package]] +name = "garde_derive" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7f0545bbbba0a37d4d445890fa5759814e0716f02417b39f6fab292193df68" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", ] [[package]] @@ -3213,6 +3238,35 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "renderdoc-sys" version = "1.1.0" diff --git a/3/coursework/src/app/authentication/Cargo.toml b/3/coursework/src/app/authentication/Cargo.toml new file mode 100644 index 0000000..3fafecd --- /dev/null +++ b/3/coursework/src/app/authentication/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "authentication" +version = "0.1.0" +edition = "2024" + +[dependencies] +argon2 = { version = "0.5.3", features = ["std"] } + +thiserror = "2.0.11" +garde = { version = "0.22.0", features = ["email", "url", "derive"] } +derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "into"] } + +[dependencies.database] +path = "../../database" diff --git a/3/coursework/src/app/authentication/src/lib.rs b/3/coursework/src/app/authentication/src/lib.rs new file mode 100644 index 0000000..1a8b0c9 --- /dev/null +++ b/3/coursework/src/app/authentication/src/lib.rs @@ -0,0 +1,4 @@ +// use database::port::user; + +mod repository; +mod service; diff --git a/3/coursework/src/app/authentication/src/repository.rs b/3/coursework/src/app/authentication/src/repository.rs new file mode 100644 index 0000000..9cd74ea --- /dev/null +++ b/3/coursework/src/app/authentication/src/repository.rs @@ -0,0 +1,86 @@ +pub use database::port::user::*; + +use derive_more::{Deref, DerefMut}; + +#[derive(Deref, DerefMut)] +pub struct Authenticated(User); + +#[allow(async_fn_in_trait)] +pub trait AuthenticationRepository { + async fn get_user(&self, get: Get) -> Result>; + async fn create_user(&self, new: New) -> Result; + async fn start_session(&self, user: User) -> Result; +} + + +pub enum Get { + Name(Name), + Email(Email), +} + +impl From for Unique { + fn from(value: Get) -> Self { + match value { + Get::Name(s) => Self::Name(s), + Get::Email(s) => Self::Email(s), + } + } +} + +// Adapter + +use database::connect::Connect; +use std::marker::PhantomData; + +pub struct AuthenticationAdapter +where + D: Connect, + UR: UserRepository, +{ + driver: D, + _user_repository: PhantomData, +} + +impl AuthenticationAdapter +where + D: Connect, + UR: UserRepository, +{ + pub const fn new(driver: D) -> Self { + Self { + driver, + _user_repository: PhantomData, + } + } +} + +impl AuthenticationRepository for AuthenticationAdapter +where + D: Connect, + UR: UserRepository, +{ + async fn get_user(&self, get: Get) -> Result> { + let c = self.driver.open_connection().await?; + let user = UR::read(&c, get.into()).await?; + D::close_connection(c).await?; + + Ok(user) + } + + async fn create_user(&self, new: New) -> Result { + let mut c = self.driver.open_connection().await?; + let user = UR::create(&mut c, new).await?; + D::close_connection(c).await?; + + Ok(user) + } + + async fn start_session(&self, mut user: User) -> Result { + let mut c = self.driver.open_connection().await?; + UR::update(&mut c, &mut user, Field::LastUsed(Some(Utc::now()))).await?; + D::close_connection(c).await?; + + Ok(Authenticated(user)) + } +} + diff --git a/3/coursework/src/app/authentication/src/service.rs b/3/coursework/src/app/authentication/src/service.rs new file mode 100644 index 0000000..0e7fa60 --- /dev/null +++ b/3/coursework/src/app/authentication/src/service.rs @@ -0,0 +1,212 @@ +use crate::repository::{Authenticated, AuthenticationRepository}; +use database::port::user; + +use derive_more::{Deref, Into}; +use garde::Validate; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + // Login + #[error("login not found")] + LoginNotFound, + #[error("incorrect password")] + IncorrectPassword, + // Register + #[error("username is taken")] + NameExists, + #[error("email is already in use")] + EmailExists, + // Shared + #[error("invalid password: {0}")] + InvalidPassword(Box), + #[error("data source error: {0}")] + Repository(Box), +} + +pub type Result = std::result::Result; + +#[derive(Clone, Deref, Into)] +pub struct Name(user::Name); + +impl TryFrom for Name { + type Error = Box; + + fn try_from(value: String) -> std::result::Result { + #[derive(Validate)] + #[garde(transparent)] + struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str); + + Username(&value).validate()?; + Ok(Self(user::Name::try_from(value)?)) + } +} + +#[derive(Clone, Deref, Into)] +pub struct Email(user::Email); +impl TryFrom for Email { + type Error = Box; + + fn try_from(value: String) -> std::result::Result { + #[derive(Validate)] + #[garde(transparent)] + pub struct Email<'a>(#[garde(email, length(chars, max = 255))] &'a str); + + Email(&value).validate()?; + Ok(Self(user::Email::try_from(value)?)) + } +} + +// #[derive(Validate, Deref, From, Into)] +// #[garde(transparent)] +// pub struct Password(#[garde(length(chars, min = 8))] pub String); + +pub enum LoginBy { + Name(Name), + Email(Email), +} + +pub struct LoginData { + login: LoginBy, + password: String, +} + +pub struct RegisterData { + pub name: Name, + pub email: Email, + pub password: String, +} + +pub trait AuthenticationContract { + async fn name_available(&self, name: Name) -> Result; + async fn email_available(&self, email: Email) -> Result; + + async fn login(&mut self, data: LoginData) -> Result; + async fn register(&mut self, data: RegisterData) -> Result; +} + +// Service + +use crate::repository::Get; + +use argon2::{ + Argon2, + password_hash::{ + self, PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng, + }, +}; + +impl From for Error { + fn from(error: password_hash::Error) -> Self { + match error { + password_hash::Error::Password => Self::IncorrectPassword, + _ => Self::InvalidPassword(error.into()), + } + } +} + +pub struct AuthenticationService +where + R: AuthenticationRepository, +{ + repository: R, +} + +impl AuthenticationService +where + R: AuthenticationRepository, +{ + pub const fn new(repository: R) -> Self { + Self { repository } + } +} + +impl AuthenticationContract for AuthenticationService +where + R: AuthenticationRepository, +{ + async fn name_available(&self, name: Name) -> Result { + if self + .repository + .get_user(Get::Name(name.0)) + .await + .map_err(Error::Repository)? + .is_some() + { + return Err(Error::NameExists); + }; + Ok(()) + } + async fn email_available(&self, email: Email) -> Result { + if self + .repository + .get_user(Get::Email(email.0)) + .await + .map_err(Error::Repository)? + .is_some() + { + return Err(Error::EmailExists); + }; + Ok(()) + } + + async fn login(&mut self, data: LoginData) -> Result { + if data.password.chars().count() < 8 { + return Err(Error::InvalidPassword( + "password must be longer than 8 characters".into(), + )); + } + + let user = match data.login { + LoginBy::Name(name) => self.repository.get_user(Get::Name(name.0)), + LoginBy::Email(email) => self.repository.get_user(Get::Email(email.0)), + } + .await + .map_err(Error::Repository)? + .ok_or(Error::LoginNotFound)?; + + Argon2::default().verify_password( + data.password.as_bytes(), + &PasswordHash::new(user.password())?, + )?; + + self.repository + .start_session(user) + .await + .map_err(Error::Repository) + } + + async fn register(&mut self, data: RegisterData) -> Result { + if data.password.chars().count() < 8 { + return Err(Error::InvalidPassword( + "password must be longer than 8 characters".into(), + )); + } + + self.name_available(data.name.clone()).await?; + self.email_available(data.email.clone()).await?; + + // Get PHC string ($argon2id$v=19$...) + let password = Argon2::default() + .hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng))? + .to_string() + .try_into() + .map_err(|e| Error::InvalidPassword(Box::from(e)))?; + + let user = self + .repository + .create_user(user::New { + name: data.name.0, + email: data.email.0, + password, + last_used: None, + }) + .await + .map_err(Error::Repository)?; + + self.repository + .start_session(user) + .await + .map_err(Error::Repository) + } +} + diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs index 89c114e..ad0988d 100644 --- a/3/coursework/src/database/src/port.rs +++ b/3/coursework/src/database/src/port.rs @@ -1,4 +1,4 @@ -pub type Result = std::result::Result>; +pub type Result> = std::result::Result; #[allow(async_fn_in_trait)] pub trait CRUD { @@ -17,7 +17,34 @@ pub trait CRUD { async fn delete(connection: &mut C, data: Self::Unique) -> Result; } -const TOO_LONG: &str = "too long"; +trait CharLength { + fn length(&self) -> usize; +} +impl CharLength for String { + fn length(&self) -> usize { + self.chars().count() + } +} +impl CharLength for Option { + fn length(&self) -> usize { + self.as_ref().map_or(0, CharLength::length) + } +} + +trait MaxLength { + type Inner: CharLength; + const MAX_LENGTH: usize; + + fn validate(value: &Self::Inner) -> Result<(), &'static str> { + if value.length() > Self::MAX_LENGTH { + Err("too long") + } else { + Ok(()) + } + } +} + +// const TOO_LONG: &str = "too long"; pub mod base; pub mod package; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs index bdc18ab..e228966 100644 --- a/3/coursework/src/database/src/port/base.rs +++ b/3/coursework/src/database/src/port/base.rs @@ -1,3 +1,4 @@ +use super::MaxLength; pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; @@ -14,29 +15,30 @@ pub trait BaseRepository: #[derive(Clone, Deref, Into)] pub struct Name(String); +impl MaxLength for Name { + type Inner = String; + const MAX_LENGTH: usize = 127; +} impl TryFrom for Name { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 127 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct Description(Option); +impl MaxLength for Description { + type Inner = Option; + const MAX_LENGTH: usize = 510; +} impl TryFrom> for Description { type Error = &'static str; - fn try_from(value: Option) -> std::result::Result { - if let Some(x) = &value { - if x.chars().count() > 510 { - return Err(super::TOO_LONG); - } - } + fn try_from(value: Option) -> Result { + Self::validate(&value)?; Ok(Self(value)) } } diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs index f37d852..d4f6859 100644 --- a/3/coursework/src/database/src/port/package.rs +++ b/3/coursework/src/database/src/port/package.rs @@ -1,3 +1,4 @@ +use super::MaxLength; pub use super::{CRUD, Result, base::Base}; pub use chrono::{DateTime, Utc}; @@ -11,58 +12,60 @@ pub trait PackageRepository: #[derive(Clone, Deref, Into)] pub struct Name(String); +impl MaxLength for Name { + type Inner = String; + const MAX_LENGTH: usize = 127; +} impl TryFrom for Name { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 127 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct Version(String); +impl MaxLength for Version { + type Inner = String; + const MAX_LENGTH: usize = 127; +} impl TryFrom for Version { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 127 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct Description(Option); +impl MaxLength for Description { + type Inner = Option; + const MAX_LENGTH: usize = 255; +} impl TryFrom> for Description { type Error = &'static str; - fn try_from(value: Option) -> std::result::Result { - if let Some(x) = &value { - if x.chars().count() > 255 { - return Err(super::TOO_LONG); - } - } + fn try_from(value: Option) -> Result { + Self::validate(&value)?; Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct URL(Option); +impl MaxLength for URL { + type Inner = Option; + const MAX_LENGTH: usize = 510; +} impl TryFrom> for URL { type Error = &'static str; - fn try_from(value: Option) -> std::result::Result { - if let Some(x) = &value { - if x.chars().count() > 510 { - return Err(super::TOO_LONG); - } - } + fn try_from(value: Option) -> Result { + Self::validate(&value)?; Ok(Self(value)) } } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs index 64f096d..b4d4141 100644 --- a/3/coursework/src/database/src/port/user.rs +++ b/3/coursework/src/database/src/port/user.rs @@ -1,3 +1,4 @@ +use super::MaxLength; pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; @@ -11,43 +12,46 @@ pub trait UserRepository: #[derive(Clone, Deref, Into)] pub struct Name(String); +impl MaxLength for Name { + type Inner = String; + const MAX_LENGTH: usize = 31; +} impl TryFrom for Name { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 31 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct Email(String); +impl MaxLength for Email { + type Inner = String; + const MAX_LENGTH: usize = 255; +} impl TryFrom for Email { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 255 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } #[derive(Clone, Deref, Into)] pub struct Password(String); +impl MaxLength for Password { + type Inner = String; + const MAX_LENGTH: usize = 255; +} impl TryFrom for Password { type Error = &'static str; - fn try_from(value: String) -> std::result::Result { - if value.chars().count() > 255 { - Err(super::TOO_LONG) - } else { - Ok(Self(value)) - } + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) } } From da50312a40c0c1b6884e7fc96375a4eb68737e45 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Sat, 1 Feb 2025 11:51:58 +0200 Subject: [PATCH 06/13] Authentication service & Send bounds for async --- .../src/app/authentication/src/repository.rs | 24 +++++++++---------- .../src/database/src/adapter/mysql/base.rs | 8 ++++++- .../src/database/src/adapter/mysql/package.rs | 8 ++++++- .../src/database/src/adapter/mysql/user.rs | 8 ++++++- 3/coursework/src/database/src/atomic.rs | 7 +++--- 3/coursework/src/database/src/connect.rs | 5 ++-- 3/coursework/src/database/src/lib.rs | 14 ----------- 3/coursework/src/database/src/port.rs | 17 ++++++++----- 3/coursework/src/database/src/port/base.rs | 1 - 3/coursework/src/database/src/port/package.rs | 1 - 3/coursework/src/database/src/port/user.rs | 1 - 11 files changed, 49 insertions(+), 45 deletions(-) diff --git a/3/coursework/src/app/authentication/src/repository.rs b/3/coursework/src/app/authentication/src/repository.rs index 9cd74ea..766731b 100644 --- a/3/coursework/src/app/authentication/src/repository.rs +++ b/3/coursework/src/app/authentication/src/repository.rs @@ -5,14 +5,12 @@ use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut)] pub struct Authenticated(User); -#[allow(async_fn_in_trait)] pub trait AuthenticationRepository { - async fn get_user(&self, get: Get) -> Result>; - async fn create_user(&self, new: New) -> Result; - async fn start_session(&self, user: User) -> Result; + fn get_user(&self, get: Get) -> impl Future>> + Send; + fn create_user(&self, new: New) -> impl Future> + Send; + fn start_session(&self, user: User) -> impl Future> + Send; } - pub enum Get { Name(Name), Email(Email), @@ -34,8 +32,9 @@ use std::marker::PhantomData; pub struct AuthenticationAdapter where - D: Connect, - UR: UserRepository, + C: Send, + D: Connect + Sync, + UR: UserRepository + Sync, { driver: D, _user_repository: PhantomData, @@ -43,8 +42,9 @@ where impl AuthenticationAdapter where - D: Connect, - UR: UserRepository, + C: Send, + D: Connect + Sync, + UR: UserRepository + Sync, { pub const fn new(driver: D) -> Self { Self { @@ -56,8 +56,9 @@ where impl AuthenticationRepository for AuthenticationAdapter where - D: Connect, - UR: UserRepository, + C: Send, + D: Connect + Sync, + UR: UserRepository + Sync, { async fn get_user(&self, get: Get) -> Result> { let c = self.driver.open_connection().await?; @@ -83,4 +84,3 @@ where Ok(Authenticated(user)) } } - diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/database/src/adapter/mysql/base.rs index 9eaa45d..02d358e 100644 --- a/3/coursework/src/database/src/adapter/mysql/base.rs +++ b/3/coursework/src/database/src/adapter/mysql/base.rs @@ -4,9 +4,15 @@ use sqlx::{Executor, MySql}; pub struct BaseAdapter; -impl BaseRepository for BaseAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl BaseRepository for BaseAdapter +where + E: Send, + for<'a> &'a E: Executor<'a, Database = MySql>, +{ +} impl crate::port::CRUD for BaseAdapter where + E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, { type New = New; diff --git a/3/coursework/src/database/src/adapter/mysql/package.rs b/3/coursework/src/database/src/adapter/mysql/package.rs index 8ea6aea..9819f30 100644 --- a/3/coursework/src/database/src/adapter/mysql/package.rs +++ b/3/coursework/src/database/src/adapter/mysql/package.rs @@ -4,9 +4,15 @@ use sqlx::{Executor, MySql}; pub struct PackageAdapter; -impl PackageRepository for PackageAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl PackageRepository for PackageAdapter +where + E: Send, + for<'a> &'a E: Executor<'a, Database = MySql>, +{ +} impl crate::port::CRUD for PackageAdapter where + E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, { type New = New; diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/database/src/adapter/mysql/user.rs index e647e47..9c06555 100644 --- a/3/coursework/src/database/src/adapter/mysql/user.rs +++ b/3/coursework/src/database/src/adapter/mysql/user.rs @@ -4,9 +4,15 @@ use sqlx::{Executor, MySql}; pub struct UserAdapter; -impl UserRepository for UserAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl UserRepository for UserAdapter +where + E: Send, + for<'a> &'a E: Executor<'a, Database = MySql>, +{ +} impl crate::port::CRUD for UserAdapter where + E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, { type New = New; diff --git a/3/coursework/src/database/src/atomic.rs b/3/coursework/src/database/src/atomic.rs index 63721aa..cfb5cfe 100644 --- a/3/coursework/src/database/src/atomic.rs +++ b/3/coursework/src/database/src/atomic.rs @@ -1,12 +1,11 @@ type Result = std::result::Result>; -#[allow(async_fn_in_trait)] pub trait Atomic { type Transaction<'a>; - async fn start_transaction(&mut self) -> Result>; - async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result; - async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result; + fn start_transaction(&mut self) -> impl Future>> + Send; + fn abort_transaction(transaction: Self::Transaction<'_>) -> impl Future + Send; + fn commit_transaction(transaction: Self::Transaction<'_>) -> impl Future + Send; } use sqlx::Connection; diff --git a/3/coursework/src/database/src/connect.rs b/3/coursework/src/database/src/connect.rs index b8bc3b5..9c4ecb5 100644 --- a/3/coursework/src/database/src/connect.rs +++ b/3/coursework/src/database/src/connect.rs @@ -1,11 +1,10 @@ type Result = std::result::Result>; -#[allow(async_fn_in_trait)] pub trait Connect { type Connection; - async fn open_connection(&self) -> Result; - async fn close_connection(connection: Self::Connection) -> Result; + fn open_connection(&self) -> impl Future> + Send; + fn close_connection(connection: Self::Connection) -> impl Future + Send; } use sqlx::Connection; diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs index 0108ec5..b66abeb 100644 --- a/3/coursework/src/database/src/lib.rs +++ b/3/coursework/src/database/src/lib.rs @@ -2,17 +2,3 @@ pub mod adapter; pub mod atomic; pub mod connect; pub mod port; - -// use garde::{Report, Unvalidated, Valid, Validate}; - -// pub trait IntoValid: Validate { -// fn into_valid(self) -> Result, Report> -// where -// Self: Sized, -// ::Context: Default, -// { -// Unvalidated::new(self).validate() -// } -// } -// impl IntoValid for T {} - diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs index ad0988d..5daecd5 100644 --- a/3/coursework/src/database/src/port.rs +++ b/3/coursework/src/database/src/port.rs @@ -1,20 +1,25 @@ pub type Result> = std::result::Result; -#[allow(async_fn_in_trait)] pub trait CRUD { type New; type Unique; type Update; type Existing; - async fn create(connection: &mut C, data: Self::New) -> Result; - async fn read(connection: &C, data: Self::Unique) -> Result>; - async fn update( + fn create( + connection: &mut C, + data: Self::New, + ) -> impl Future> + Send; + fn read( + connection: &C, + data: Self::Unique, + ) -> impl Future>> + Send; + fn update( connection: &mut C, existing: &mut Self::Existing, data: Self::Update, - ) -> Result; - async fn delete(connection: &mut C, data: Self::Unique) -> Result; + ) -> impl Future + Send; + fn delete(connection: &mut C, data: Self::Unique) -> impl Future + Send; } trait CharLength { diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs index e228966..c312733 100644 --- a/3/coursework/src/database/src/port/base.rs +++ b/3/coursework/src/database/src/port/base.rs @@ -4,7 +4,6 @@ pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; -#[allow(async_fn_in_trait)] pub trait BaseRepository: CRUD { diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs index d4f6859..7d4029b 100644 --- a/3/coursework/src/database/src/port/package.rs +++ b/3/coursework/src/database/src/port/package.rs @@ -4,7 +4,6 @@ pub use super::{CRUD, Result, base::Base}; pub use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; -#[allow(async_fn_in_trait)] pub trait PackageRepository: CRUD { diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs index b4d4141..fb49757 100644 --- a/3/coursework/src/database/src/port/user.rs +++ b/3/coursework/src/database/src/port/user.rs @@ -4,7 +4,6 @@ pub use super::{CRUD, Result}; pub use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; -#[allow(async_fn_in_trait)] pub trait UserRepository: CRUD { From 88cd107e14c1d8931f23a20c8ad9bc66c2f2f5a1 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Sun, 2 Feb 2025 22:41:32 +0200 Subject: [PATCH 07/13] Reorganize & implement Authentication view api --- 3/coursework/src/.gitignore | 1 - 3/coursework/src/Cargo.lock | 45 ++-- 3/coursework/src/Cargo.toml | 2 +- .../src/app/authentication/src/lib.rs | 4 - .../src/app/authentication/src/service.rs | 212 ----------------- .../src/{database => assets}/compose.yaml | 2 +- .../src/{database => assets}/init/init.sql | 0 ...85df75ae8e3c95a1a7c6c21be7b5624a82ae1.json | 0 ...4472929e6b2618ab13eb90e772ad9bd9c1984.json | 0 ...efca62c247c4b2265a9667c4b33885126c771.json | 0 ...4513493670e3f2ae017d87a2f0c3eca045f60.json | 0 ...190ac327ae79f6e555def8fec89fcc75fb015.json | 0 ...0a468da889a3455c47325938b819ab41ef4c8.json | 0 ...63594bb8273e66c053934ec20d5fc3db9d41e.json | 0 ...24b553d8ac922da251d9e8b8f4e897bab46b0.json | 0 ...4a9cd92aaea3ea58ef8702903983cfc32ab47.json | 0 ...a79a09f3e3d8cfd42d3946bd1fac93838b913.json | 0 ...5f70903a38ed314235712c28ac5e14d9ac20f.json | 0 ...8b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json | 0 ...e921f719ed9466c13713fda8736c540d0fa78.json | 0 ...11134ec0f43504d3a820698282848fd67dbad.json | 0 ...49849d29af3d6345d0e02474abf4d8c78b89d.json | 0 ...33588c0679404a68a8041f414553226abeeb2.json | 0 ...4db874788ed297fe2d95275592becb186f942.json | 0 ...e6efb16919aff7172189c81240cb12462ae58.json | 0 ...8d28035747b66e57260bc9cd4634a71a037a6.json | 0 ...6010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json | 0 ...6b0b9ae811ba6bff0617871e76de3ef0ff662.json | 0 ...ef4fcd78f8571cfe2347c147124763bd17491.json | 0 ...a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json | 0 ...8f9b092d74c2298ac126f7edbb7d59c536910.json | 0 ...b7aa98b84baf911e7f779641c1dc514c676cd.json | 0 ...dd8dccd1a4e6a763963590c904c25abf33137.json | 0 ...1546f10e8213d36b6b3cd25016f829e9d731b.json | 0 ...a562a3c4a4569032e8636d664043b5dc96661.json | 0 ...90e92bccd89d1ff90131719c40626088dabd1.json | 0 ...8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json | 0 ...8971cb804a1ab1edcdae8bf009ac39059c2bb.json | 0 ...b7ee842c26c44da32ae9bbbc06c466a908ccf.json | 0 ...4a364988f5329356282f2ae0098dbfcaec671.json | 0 .../src/{database => data}/Cargo.toml | 2 +- 3/coursework/src/data/src/adapter.rs | 2 + .../{database => data}/src/adapter/mysql.rs | 1 + .../src/adapter/mysql/base.rs | 4 +- .../src/adapter/mysql/package.rs | 4 +- .../src/adapter/mysql/user.rs | 4 +- .../src/{database => data}/src/atomic.rs | 10 +- .../src/{database => data}/src/connect.rs | 26 ++- 3/coursework/src/data/src/lib.rs | 21 ++ .../src/{database => data}/src/port.rs | 24 +- .../src/{database => data}/src/port/base.rs | 13 +- .../{database => data}/src/port/package.rs | 38 +-- .../src/{database => data}/src/port/user.rs | 30 ++- 3/coursework/src/database/src/adapter.rs | 1 - 3/coursework/src/database/src/lib.rs | 4 - .../authentication => service}/Cargo.toml | 9 +- .../src/service/src/authentication.rs | 9 + .../src/authentication/adapter.rs} | 34 +-- .../service/src/authentication/contract.rs | 122 ++++++++++ .../service/src/authentication/repository.rs | 26 +++ .../src/service/src/authentication/service.rs | 114 +++++++++ 3/coursework/src/service/src/lib.rs | 6 + 3/coursework/src/view/Cargo.toml | 7 + 3/coursework/src/view/src/authentication.rs | 104 +++++++++ .../src/view/src/authentication/login.rs | 164 +++++++++++++ .../src/view/src/authentication/register.rs | 216 ++++++++++++++++++ 3/coursework/src/view/src/input.rs | 93 ++++++++ 3/coursework/src/view/src/lib.rs | 7 + 3/coursework/src/view/src/widget.rs | 71 ++++++ 69 files changed, 1089 insertions(+), 343 deletions(-) delete mode 100644 3/coursework/src/app/authentication/src/lib.rs delete mode 100644 3/coursework/src/app/authentication/src/service.rs rename 3/coursework/src/{database => assets}/compose.yaml (84%) rename 3/coursework/src/{database => assets}/init/init.sql (100%) rename 3/coursework/src/{database => data}/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json (100%) rename 3/coursework/src/{database => data}/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json (100%) rename 3/coursework/src/{database => data}/Cargo.toml (96%) create mode 100644 3/coursework/src/data/src/adapter.rs rename 3/coursework/src/{database => data}/src/adapter/mysql.rs (67%) rename 3/coursework/src/{database => data}/src/adapter/mysql/base.rs (96%) rename 3/coursework/src/{database => data}/src/adapter/mysql/package.rs (97%) rename 3/coursework/src/{database => data}/src/adapter/mysql/user.rs (97%) rename 3/coursework/src/{database => data}/src/atomic.rs (79%) rename 3/coursework/src/{database => data}/src/connect.rs (60%) create mode 100644 3/coursework/src/data/src/lib.rs rename 3/coursework/src/{database => data}/src/port.rs (67%) rename 3/coursework/src/{database => data}/src/port/base.rs (85%) rename 3/coursework/src/{database => data}/src/port/package.rs (76%) rename 3/coursework/src/{database => data}/src/port/user.rs (76%) delete mode 100644 3/coursework/src/database/src/adapter.rs delete mode 100644 3/coursework/src/database/src/lib.rs rename 3/coursework/src/{app/authentication => service}/Cargo.toml (79%) create mode 100644 3/coursework/src/service/src/authentication.rs rename 3/coursework/src/{app/authentication/src/repository.rs => service/src/authentication/adapter.rs} (62%) create mode 100644 3/coursework/src/service/src/authentication/contract.rs create mode 100644 3/coursework/src/service/src/authentication/repository.rs create mode 100644 3/coursework/src/service/src/authentication/service.rs create mode 100644 3/coursework/src/service/src/lib.rs create mode 100644 3/coursework/src/view/Cargo.toml create mode 100644 3/coursework/src/view/src/authentication.rs create mode 100644 3/coursework/src/view/src/authentication/login.rs create mode 100644 3/coursework/src/view/src/authentication/register.rs create mode 100644 3/coursework/src/view/src/input.rs create mode 100644 3/coursework/src/view/src/lib.rs create mode 100644 3/coursework/src/view/src/widget.rs diff --git a/3/coursework/src/.gitignore b/3/coursework/src/.gitignore index edb52da..b59bb3c 100644 --- a/3/coursework/src/.gitignore +++ b/3/coursework/src/.gitignore @@ -1,3 +1,2 @@ /target/ /**/scrapyard/ -/database/data/ diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 679dee7..8431ca4 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -326,17 +326,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "authentication" -version = "0.1.0" -dependencies = [ - "argon2", - "database", - "derive_more", - "garde", - "thiserror 2.0.11", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -911,7 +900,7 @@ dependencies = [ ] [[package]] -name = "database" +name = "data" version = "0.1.0" dependencies = [ "chrono", @@ -1791,6 +1780,7 @@ dependencies = [ "iced_core", "log", "rustc-hash 2.1.0", + "tokio", "wasm-bindgen-futures", "wasm-timer", ] @@ -2260,18 +2250,12 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -[[package]] -name = "macros" -version = "0.1.0" - [[package]] name = "main" version = "0.1.0" dependencies = [ "iced", - "macros", "strum", - "widget", ] [[package]] @@ -3464,6 +3448,17 @@ dependencies = [ "serde", ] +[[package]] +name = "service" +version = "0.1.0" +dependencies = [ + "argon2", + "data", + "derive_more", + "garde", + "thiserror 2.0.11", +] + [[package]] name = "sha1" version = "0.10.6" @@ -4323,6 +4318,13 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "view" +version = "0.1.0" +dependencies = [ + "iced", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4692,13 +4694,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" -[[package]] -name = "widget" -version = "0.1.0" -dependencies = [ - "iced", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml index 78b5bb8..99ee3bb 100644 --- a/3/coursework/src/Cargo.toml +++ b/3/coursework/src/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["app/*", "libs/*", "database"] +members = ["data", "main", "service", "view"] diff --git a/3/coursework/src/app/authentication/src/lib.rs b/3/coursework/src/app/authentication/src/lib.rs deleted file mode 100644 index 1a8b0c9..0000000 --- a/3/coursework/src/app/authentication/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -// use database::port::user; - -mod repository; -mod service; diff --git a/3/coursework/src/app/authentication/src/service.rs b/3/coursework/src/app/authentication/src/service.rs deleted file mode 100644 index 0e7fa60..0000000 --- a/3/coursework/src/app/authentication/src/service.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::repository::{Authenticated, AuthenticationRepository}; -use database::port::user; - -use derive_more::{Deref, Into}; -use garde::Validate; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - // Login - #[error("login not found")] - LoginNotFound, - #[error("incorrect password")] - IncorrectPassword, - // Register - #[error("username is taken")] - NameExists, - #[error("email is already in use")] - EmailExists, - // Shared - #[error("invalid password: {0}")] - InvalidPassword(Box), - #[error("data source error: {0}")] - Repository(Box), -} - -pub type Result = std::result::Result; - -#[derive(Clone, Deref, Into)] -pub struct Name(user::Name); - -impl TryFrom for Name { - type Error = Box; - - fn try_from(value: String) -> std::result::Result { - #[derive(Validate)] - #[garde(transparent)] - struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str); - - Username(&value).validate()?; - Ok(Self(user::Name::try_from(value)?)) - } -} - -#[derive(Clone, Deref, Into)] -pub struct Email(user::Email); -impl TryFrom for Email { - type Error = Box; - - fn try_from(value: String) -> std::result::Result { - #[derive(Validate)] - #[garde(transparent)] - pub struct Email<'a>(#[garde(email, length(chars, max = 255))] &'a str); - - Email(&value).validate()?; - Ok(Self(user::Email::try_from(value)?)) - } -} - -// #[derive(Validate, Deref, From, Into)] -// #[garde(transparent)] -// pub struct Password(#[garde(length(chars, min = 8))] pub String); - -pub enum LoginBy { - Name(Name), - Email(Email), -} - -pub struct LoginData { - login: LoginBy, - password: String, -} - -pub struct RegisterData { - pub name: Name, - pub email: Email, - pub password: String, -} - -pub trait AuthenticationContract { - async fn name_available(&self, name: Name) -> Result; - async fn email_available(&self, email: Email) -> Result; - - async fn login(&mut self, data: LoginData) -> Result; - async fn register(&mut self, data: RegisterData) -> Result; -} - -// Service - -use crate::repository::Get; - -use argon2::{ - Argon2, - password_hash::{ - self, PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng, - }, -}; - -impl From for Error { - fn from(error: password_hash::Error) -> Self { - match error { - password_hash::Error::Password => Self::IncorrectPassword, - _ => Self::InvalidPassword(error.into()), - } - } -} - -pub struct AuthenticationService -where - R: AuthenticationRepository, -{ - repository: R, -} - -impl AuthenticationService -where - R: AuthenticationRepository, -{ - pub const fn new(repository: R) -> Self { - Self { repository } - } -} - -impl AuthenticationContract for AuthenticationService -where - R: AuthenticationRepository, -{ - async fn name_available(&self, name: Name) -> Result { - if self - .repository - .get_user(Get::Name(name.0)) - .await - .map_err(Error::Repository)? - .is_some() - { - return Err(Error::NameExists); - }; - Ok(()) - } - async fn email_available(&self, email: Email) -> Result { - if self - .repository - .get_user(Get::Email(email.0)) - .await - .map_err(Error::Repository)? - .is_some() - { - return Err(Error::EmailExists); - }; - Ok(()) - } - - async fn login(&mut self, data: LoginData) -> Result { - if data.password.chars().count() < 8 { - return Err(Error::InvalidPassword( - "password must be longer than 8 characters".into(), - )); - } - - let user = match data.login { - LoginBy::Name(name) => self.repository.get_user(Get::Name(name.0)), - LoginBy::Email(email) => self.repository.get_user(Get::Email(email.0)), - } - .await - .map_err(Error::Repository)? - .ok_or(Error::LoginNotFound)?; - - Argon2::default().verify_password( - data.password.as_bytes(), - &PasswordHash::new(user.password())?, - )?; - - self.repository - .start_session(user) - .await - .map_err(Error::Repository) - } - - async fn register(&mut self, data: RegisterData) -> Result { - if data.password.chars().count() < 8 { - return Err(Error::InvalidPassword( - "password must be longer than 8 characters".into(), - )); - } - - self.name_available(data.name.clone()).await?; - self.email_available(data.email.clone()).await?; - - // Get PHC string ($argon2id$v=19$...) - let password = Argon2::default() - .hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng))? - .to_string() - .try_into() - .map_err(|e| Error::InvalidPassword(Box::from(e)))?; - - let user = self - .repository - .create_user(user::New { - name: data.name.0, - email: data.email.0, - password, - last_used: None, - }) - .await - .map_err(Error::Repository)?; - - self.repository - .start_session(user) - .await - .map_err(Error::Repository) - } -} - diff --git a/3/coursework/src/database/compose.yaml b/3/coursework/src/assets/compose.yaml similarity index 84% rename from 3/coursework/src/database/compose.yaml rename to 3/coursework/src/assets/compose.yaml index 5c2cec9..3faafd3 100644 --- a/3/coursework/src/database/compose.yaml +++ b/3/coursework/src/assets/compose.yaml @@ -5,7 +5,7 @@ services: ports: - "127.0.0.1:3306:3306" volumes: - - ./data:/var/lib/mysql + - ../../repo-database:/var/lib/mysql - ./init:/docker-entrypoint-initdb.d/:ro environment: MYSQL_ROOT_PASSWORD: password # yes, I know diff --git a/3/coursework/src/database/init/init.sql b/3/coursework/src/assets/init/init.sql similarity index 100% rename from 3/coursework/src/database/init/init.sql rename to 3/coursework/src/assets/init/init.sql diff --git a/3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json b/3/coursework/src/data/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json rename to 3/coursework/src/data/.sqlx/query-014cf2ec55142a17047ad7c469685df75ae8e3c95a1a7c6c21be7b5624a82ae1.json diff --git a/3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json b/3/coursework/src/data/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json rename to 3/coursework/src/data/.sqlx/query-063059de083c42956506d991bc04472929e6b2618ab13eb90e772ad9bd9c1984.json diff --git a/3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json b/3/coursework/src/data/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json rename to 3/coursework/src/data/.sqlx/query-0af939868e37bad5eb9097badeaefca62c247c4b2265a9667c4b33885126c771.json diff --git a/3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json b/3/coursework/src/data/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json rename to 3/coursework/src/data/.sqlx/query-0bb7353d64231dc12416f5504d94513493670e3f2ae017d87a2f0c3eca045f60.json diff --git a/3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json b/3/coursework/src/data/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json rename to 3/coursework/src/data/.sqlx/query-346beb83d6351740a503b72133a190ac327ae79f6e555def8fec89fcc75fb015.json diff --git a/3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json b/3/coursework/src/data/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json rename to 3/coursework/src/data/.sqlx/query-389e38e7e0a0b7d9ba667ac148a0a468da889a3455c47325938b819ab41ef4c8.json diff --git a/3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json b/3/coursework/src/data/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json rename to 3/coursework/src/data/.sqlx/query-404747d44ee859e8c967695c29963594bb8273e66c053934ec20d5fc3db9d41e.json diff --git a/3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json b/3/coursework/src/data/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json rename to 3/coursework/src/data/.sqlx/query-68ed36ae997fff190b4b15b80bf24b553d8ac922da251d9e8b8f4e897bab46b0.json diff --git a/3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json b/3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json rename to 3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json diff --git a/3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json b/3/coursework/src/data/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json rename to 3/coursework/src/data/.sqlx/query-7cc4cf73572c0830d1da7b8e621a79a09f3e3d8cfd42d3946bd1fac93838b913.json diff --git a/3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json b/3/coursework/src/data/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json rename to 3/coursework/src/data/.sqlx/query-7f06016e9892486c938a5e94c9e5f70903a38ed314235712c28ac5e14d9ac20f.json diff --git a/3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json b/3/coursework/src/data/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json rename to 3/coursework/src/data/.sqlx/query-839cea68f9de889f35a0d0ad0b48b4a0dc1af49f0f0e7bb12238d22a9c37fbbc.json diff --git a/3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json b/3/coursework/src/data/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json rename to 3/coursework/src/data/.sqlx/query-8af7a0169e934cb82997a1cab04e921f719ed9466c13713fda8736c540d0fa78.json diff --git a/3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json b/3/coursework/src/data/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json rename to 3/coursework/src/data/.sqlx/query-8be76176b46f645095dce3bcbed11134ec0f43504d3a820698282848fd67dbad.json diff --git a/3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json b/3/coursework/src/data/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json rename to 3/coursework/src/data/.sqlx/query-8bfaca937858ed1060da5a650f749849d29af3d6345d0e02474abf4d8c78b89d.json diff --git a/3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json b/3/coursework/src/data/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json rename to 3/coursework/src/data/.sqlx/query-8e3ffe0d11d3eb38cd805771cd133588c0679404a68a8041f414553226abeeb2.json diff --git a/3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json b/3/coursework/src/data/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json rename to 3/coursework/src/data/.sqlx/query-93ec7d124c9bfa7329478d975614db874788ed297fe2d95275592becb186f942.json diff --git a/3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json b/3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json rename to 3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json diff --git a/3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json b/3/coursework/src/data/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json rename to 3/coursework/src/data/.sqlx/query-9be7f66630e64787e55946dff428d28035747b66e57260bc9cd4634a71a037a6.json diff --git a/3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json b/3/coursework/src/data/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json rename to 3/coursework/src/data/.sqlx/query-9fa86328c40ce0469f755efb4876010092b7bc9f240a5d43dc69f9d0b1b5b7ce.json diff --git a/3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json b/3/coursework/src/data/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json rename to 3/coursework/src/data/.sqlx/query-b5814b93236d587957a103e61726b0b9ae811ba6bff0617871e76de3ef0ff662.json diff --git a/3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json b/3/coursework/src/data/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json rename to 3/coursework/src/data/.sqlx/query-c1abf048d65d421717f20343bb0ef4fcd78f8571cfe2347c147124763bd17491.json diff --git a/3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json b/3/coursework/src/data/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json rename to 3/coursework/src/data/.sqlx/query-c2b00adbcb3c35a6ffa6b2bce08a738a9b3cd1ca4aa4c843909c7e14f7ef3e06.json diff --git a/3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json b/3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json rename to 3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json diff --git a/3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json b/3/coursework/src/data/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json rename to 3/coursework/src/data/.sqlx/query-cc8f7e13c6aedf6aa4d6d4fc39db7aa98b84baf911e7f779641c1dc514c676cd.json diff --git a/3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json b/3/coursework/src/data/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json rename to 3/coursework/src/data/.sqlx/query-cf79e2f6038dddd055d535d2c41dd8dccd1a4e6a763963590c904c25abf33137.json diff --git a/3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json b/3/coursework/src/data/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json rename to 3/coursework/src/data/.sqlx/query-d289747c7c7fba86e2b66174e2d1546f10e8213d36b6b3cd25016f829e9d731b.json diff --git a/3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json b/3/coursework/src/data/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json rename to 3/coursework/src/data/.sqlx/query-d474dd848d0ef8832afd4d1302fa562a3c4a4569032e8636d664043b5dc96661.json diff --git a/3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json b/3/coursework/src/data/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json rename to 3/coursework/src/data/.sqlx/query-daf98e6f1013c4993f7329f6fa690e92bccd89d1ff90131719c40626088dabd1.json diff --git a/3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json b/3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json rename to 3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json diff --git a/3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json b/3/coursework/src/data/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json rename to 3/coursework/src/data/.sqlx/query-e8ee44281a87c6e7147332dd5548971cb804a1ab1edcdae8bf009ac39059c2bb.json diff --git a/3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json b/3/coursework/src/data/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json rename to 3/coursework/src/data/.sqlx/query-f4963ad77bcbc0af4fc929f1f66b7ee842c26c44da32ae9bbbc06c466a908ccf.json diff --git a/3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json b/3/coursework/src/data/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json similarity index 100% rename from 3/coursework/src/database/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json rename to 3/coursework/src/data/.sqlx/query-f656bd1abb82c10af4e0e21b4a04a364988f5329356282f2ae0098dbfcaec671.json diff --git a/3/coursework/src/database/Cargo.toml b/3/coursework/src/data/Cargo.toml similarity index 96% rename from 3/coursework/src/database/Cargo.toml rename to 3/coursework/src/data/Cargo.toml index 0ed9ab6..432c9e2 100644 --- a/3/coursework/src/database/Cargo.toml +++ b/3/coursework/src/data/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "database" +name = "data" version = "0.1.0" edition = "2024" diff --git a/3/coursework/src/data/src/adapter.rs b/3/coursework/src/data/src/adapter.rs new file mode 100644 index 0000000..37f698b --- /dev/null +++ b/3/coursework/src/data/src/adapter.rs @@ -0,0 +1,2 @@ +//! Specific implementations of [`crate::port`]s to plug into other parts of the application. +pub mod mysql; diff --git a/3/coursework/src/database/src/adapter/mysql.rs b/3/coursework/src/data/src/adapter/mysql.rs similarity index 67% rename from 3/coursework/src/database/src/adapter/mysql.rs rename to 3/coursework/src/data/src/adapter/mysql.rs index ec9df17..9d6db43 100644 --- a/3/coursework/src/database/src/adapter/mysql.rs +++ b/3/coursework/src/data/src/adapter/mysql.rs @@ -1,3 +1,4 @@ +//! `MySQL` adapters. pub mod base; pub mod package; pub mod user; diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/data/src/adapter/mysql/base.rs similarity index 96% rename from 3/coursework/src/database/src/adapter/mysql/base.rs rename to 3/coursework/src/data/src/adapter/mysql/base.rs index 02d358e..6d9fa8d 100644 --- a/3/coursework/src/database/src/adapter/mysql/base.rs +++ b/3/coursework/src/data/src/adapter/mysql/base.rs @@ -1,5 +1,7 @@ -pub use crate::port::base::*; +use crate::Result; +use crate::port::base::{Base, BaseRepository, Field, New}; +use chrono::Utc; use sqlx::{Executor, MySql}; pub struct BaseAdapter; diff --git a/3/coursework/src/database/src/adapter/mysql/package.rs b/3/coursework/src/data/src/adapter/mysql/package.rs similarity index 97% rename from 3/coursework/src/database/src/adapter/mysql/package.rs rename to 3/coursework/src/data/src/adapter/mysql/package.rs index 9819f30..b1286d1 100644 --- a/3/coursework/src/database/src/adapter/mysql/package.rs +++ b/3/coursework/src/data/src/adapter/mysql/package.rs @@ -1,5 +1,7 @@ -pub use crate::port::package::*; +use crate::Result; +use crate::port::package::{Field, New, Package, PackageRepository, Unique}; +use chrono::Utc; use sqlx::{Executor, MySql}; pub struct PackageAdapter; diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/data/src/adapter/mysql/user.rs similarity index 97% rename from 3/coursework/src/database/src/adapter/mysql/user.rs rename to 3/coursework/src/data/src/adapter/mysql/user.rs index 9c06555..ca66177 100644 --- a/3/coursework/src/database/src/adapter/mysql/user.rs +++ b/3/coursework/src/data/src/adapter/mysql/user.rs @@ -1,5 +1,7 @@ -pub use crate::port::user::*; +use crate::Result; +use crate::port::user::{Field, New, Unique, User, UserRepository}; +use chrono::Utc; use sqlx::{Executor, MySql}; pub struct UserAdapter; diff --git a/3/coursework/src/database/src/atomic.rs b/3/coursework/src/data/src/atomic.rs similarity index 79% rename from 3/coursework/src/database/src/atomic.rs rename to 3/coursework/src/data/src/atomic.rs index cfb5cfe..1141e7d 100644 --- a/3/coursework/src/database/src/atomic.rs +++ b/3/coursework/src/data/src/atomic.rs @@ -1,11 +1,15 @@ -type Result = std::result::Result>; +//! Unify transaction management for established connections. +use crate::Result; pub trait Atomic { type Transaction<'a>; fn start_transaction(&mut self) -> impl Future>> + Send; - fn abort_transaction(transaction: Self::Transaction<'_>) -> impl Future + Send; - fn commit_transaction(transaction: Self::Transaction<'_>) -> impl Future + Send; + fn abort_transaction(transaction: Self::Transaction<'_>) + -> impl Future + Send; + fn commit_transaction( + transaction: Self::Transaction<'_>, + ) -> impl Future + Send; } use sqlx::Connection; diff --git a/3/coursework/src/database/src/connect.rs b/3/coursework/src/data/src/connect.rs similarity index 60% rename from 3/coursework/src/database/src/connect.rs rename to 3/coursework/src/data/src/connect.rs index 9c4ecb5..f8e054b 100644 --- a/3/coursework/src/database/src/connect.rs +++ b/3/coursework/src/data/src/connect.rs @@ -1,4 +1,5 @@ -type Result = std::result::Result>; +//! Driver to manage a connection which is passed to adapters. +use crate::Result; pub trait Connect { type Connection; @@ -8,13 +9,20 @@ pub trait Connect { } use sqlx::Connection; +pub use sqlx::MySqlConnection as SqlxConnection; +pub use sqlx::MySqlPool as SqlxPool; #[derive(Clone)] pub struct MySqlPool { - pool: sqlx::MySqlPool, + pool: SqlxPool, +} +impl MySqlPool { + pub const fn new(pool: SqlxPool) -> Self { + Self { pool } + } } impl Connect for MySqlPool { - type Connection = sqlx::MySqlPool; + type Connection = SqlxPool; async fn open_connection(&self) -> Result { Ok(self.pool.clone()) @@ -27,14 +35,16 @@ impl Connect for MySqlPool { pub struct MySqlConnection { link: String, } - +impl MySqlConnection { + pub const fn new(link: String) -> Self { + Self { link } + } +} impl Connect for MySqlConnection { - type Connection = sqlx::MySqlConnection; + type Connection = SqlxConnection; async fn open_connection(&self) -> Result { - sqlx::MySqlConnection::connect(&self.link) - .await - .map_err(Box::from) + SqlxConnection::connect(&self.link).await.map_err(Box::from) } async fn close_connection(connection: Self::Connection) -> Result { connection.close().await?; diff --git a/3/coursework/src/data/src/lib.rs b/3/coursework/src/data/src/lib.rs new file mode 100644 index 0000000..8633dbc --- /dev/null +++ b/3/coursework/src/data/src/lib.rs @@ -0,0 +1,21 @@ +//! Data access for the application. +pub mod adapter; +pub mod atomic; +pub mod connect; +pub mod port; + +// Don't want to handle errors for dynamic mess. +pub type BoxDynError = Box; +pub type Result = std::result::Result; + +pub use chrono::Utc; + +pub use atomic::Atomic; +pub use connect::*; + +pub use adapter::mysql::base::BaseAdapter as MySqlBaseAdapter; +pub use adapter::mysql::package::PackageAdapter as MySqlPackageAdapter; +pub use adapter::mysql::user::UserAdapter as MySqlUserAdapter; +pub use port::base::{self, Base, BaseRepository}; +pub use port::package::{self, Package, PackageRepository}; +pub use port::user::{self, User, UserRepository}; diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/data/src/port.rs similarity index 67% rename from 3/coursework/src/database/src/port.rs rename to 3/coursework/src/data/src/port.rs index 5daecd5..a9b70f4 100644 --- a/3/coursework/src/database/src/port.rs +++ b/3/coursework/src/data/src/port.rs @@ -1,4 +1,11 @@ -pub type Result> = std::result::Result; +//! Low-level repository traits for unified data access. +//! +//! No data validation besides very basic one like length violation. +use crate::Result; + +pub mod base; +pub mod package; +pub mod user; pub trait CRUD { type New; @@ -9,17 +16,18 @@ pub trait CRUD { fn create( connection: &mut C, data: Self::New, - ) -> impl Future> + Send; + ) -> impl Future> + Send; fn read( connection: &C, data: Self::Unique, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; fn update( connection: &mut C, existing: &mut Self::Existing, data: Self::Update, - ) -> impl Future + Send; - fn delete(connection: &mut C, data: Self::Unique) -> impl Future + Send; + ) -> impl Future + Send; + fn delete(connection: &mut C, data: Self::Unique) + -> impl Future + Send; } trait CharLength { @@ -48,9 +56,3 @@ trait MaxLength { } } } - -// const TOO_LONG: &str = "too long"; - -pub mod base; -pub mod package; -pub mod user; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/data/src/port/base.rs similarity index 85% rename from 3/coursework/src/database/src/port/base.rs rename to 3/coursework/src/data/src/port/base.rs index c312733..d7c72fe 100644 --- a/3/coursework/src/database/src/port/base.rs +++ b/3/coursework/src/data/src/port/base.rs @@ -1,11 +1,10 @@ use super::MaxLength; -pub use super::{CRUD, Result}; -pub use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait BaseRepository: - CRUD + super::CRUD { } @@ -34,11 +33,13 @@ impl MaxLength for Description { const MAX_LENGTH: usize = 510; } impl TryFrom> for Description { - type Error = &'static str; + type Error = (Option, &'static str); fn try_from(value: Option) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/data/src/port/package.rs similarity index 76% rename from 3/coursework/src/database/src/port/package.rs rename to 3/coursework/src/data/src/port/package.rs index 7d4029b..6d7862e 100644 --- a/3/coursework/src/database/src/port/package.rs +++ b/3/coursework/src/data/src/port/package.rs @@ -1,11 +1,11 @@ use super::MaxLength; -pub use super::{CRUD, Result, base::Base}; +use crate::Base; -pub use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait PackageRepository: - CRUD + super::CRUD { } @@ -16,11 +16,13 @@ impl MaxLength for Name { const MAX_LENGTH: usize = 127; } impl TryFrom for Name { - type Error = &'static str; + type Error = (String, &'static str); fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -31,11 +33,13 @@ impl MaxLength for Version { const MAX_LENGTH: usize = 127; } impl TryFrom for Version { - type Error = &'static str; + type Error = (String, &'static str); fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -46,11 +50,13 @@ impl MaxLength for Description { const MAX_LENGTH: usize = 255; } impl TryFrom> for Description { - type Error = &'static str; + type Error = (Option, &'static str); fn try_from(value: Option) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -61,11 +67,13 @@ impl MaxLength for URL { const MAX_LENGTH: usize = 510; } impl TryFrom> for URL { - type Error = &'static str; + type Error = (Option, &'static str); fn try_from(value: Option) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/data/src/port/user.rs similarity index 76% rename from 3/coursework/src/database/src/port/user.rs rename to 3/coursework/src/data/src/port/user.rs index fb49757..359ce02 100644 --- a/3/coursework/src/database/src/port/user.rs +++ b/3/coursework/src/data/src/port/user.rs @@ -1,11 +1,10 @@ use super::MaxLength; -pub use super::{CRUD, Result}; -pub use chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait UserRepository: - CRUD + super::CRUD { } @@ -16,11 +15,13 @@ impl MaxLength for Name { const MAX_LENGTH: usize = 31; } impl TryFrom for Name { - type Error = &'static str; + type Error = (String, &'static str); fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -31,11 +32,13 @@ impl MaxLength for Email { const MAX_LENGTH: usize = 255; } impl TryFrom for Email { - type Error = &'static str; + type Error = (String, &'static str); fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -46,11 +49,13 @@ impl MaxLength for Password { const MAX_LENGTH: usize = 255; } impl TryFrom for Password { - type Error = &'static str; + type Error = (String, &'static str); fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + match Self::validate(&value) { + Ok(()) => Ok(Self(value)), + Err(e) => Err((value, e)), + } } } @@ -76,6 +81,7 @@ pub struct New { pub last_used: Option>, } +#[derive(Debug)] pub struct User { pub(crate) id: u64, pub(crate) name: String, diff --git a/3/coursework/src/database/src/adapter.rs b/3/coursework/src/database/src/adapter.rs deleted file mode 100644 index 4fb2dd9..0000000 --- a/3/coursework/src/database/src/adapter.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mysql; diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs deleted file mode 100644 index b66abeb..0000000 --- a/3/coursework/src/database/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod adapter; -pub mod atomic; -pub mod connect; -pub mod port; diff --git a/3/coursework/src/app/authentication/Cargo.toml b/3/coursework/src/service/Cargo.toml similarity index 79% rename from 3/coursework/src/app/authentication/Cargo.toml rename to 3/coursework/src/service/Cargo.toml index 3fafecd..372db4c 100644 --- a/3/coursework/src/app/authentication/Cargo.toml +++ b/3/coursework/src/service/Cargo.toml @@ -1,14 +1,13 @@ [package] -name = "authentication" +name = "service" version = "0.1.0" edition = "2024" [dependencies] -argon2 = { version = "0.5.3", features = ["std"] } - thiserror = "2.0.11" +argon2 = { version = "0.5.3", features = ["std"] } garde = { version = "0.22.0", features = ["email", "url", "derive"] } derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "into"] } -[dependencies.database] -path = "../../database" +[dependencies.data] +path = "../data" diff --git a/3/coursework/src/service/src/authentication.rs b/3/coursework/src/service/src/authentication.rs new file mode 100644 index 0000000..142e065 --- /dev/null +++ b/3/coursework/src/service/src/authentication.rs @@ -0,0 +1,9 @@ +pub mod adapter; +pub mod contract; +pub mod repository; +pub mod service; + +pub use adapter::*; +pub use contract::*; +pub use repository::*; +pub use service::*; diff --git a/3/coursework/src/app/authentication/src/repository.rs b/3/coursework/src/service/src/authentication/adapter.rs similarity index 62% rename from 3/coursework/src/app/authentication/src/repository.rs rename to 3/coursework/src/service/src/authentication/adapter.rs index 766731b..76173de 100644 --- a/3/coursework/src/app/authentication/src/repository.rs +++ b/3/coursework/src/service/src/authentication/adapter.rs @@ -1,33 +1,7 @@ -pub use database::port::user::*; +use super::{Authenticated, AuthenticationRepository, Get}; +use data::user::{Field, New, User, UserRepository}; +use data::{Connect, Result}; -use derive_more::{Deref, DerefMut}; - -#[derive(Deref, DerefMut)] -pub struct Authenticated(User); - -pub trait AuthenticationRepository { - fn get_user(&self, get: Get) -> impl Future>> + Send; - fn create_user(&self, new: New) -> impl Future> + Send; - fn start_session(&self, user: User) -> impl Future> + Send; -} - -pub enum Get { - Name(Name), - Email(Email), -} - -impl From for Unique { - fn from(value: Get) -> Self { - match value { - Get::Name(s) => Self::Name(s), - Get::Email(s) => Self::Email(s), - } - } -} - -// Adapter - -use database::connect::Connect; use std::marker::PhantomData; pub struct AuthenticationAdapter @@ -78,7 +52,7 @@ where async fn start_session(&self, mut user: User) -> Result { let mut c = self.driver.open_connection().await?; - UR::update(&mut c, &mut user, Field::LastUsed(Some(Utc::now()))).await?; + UR::update(&mut c, &mut user, Field::LastUsed(Some(data::Utc::now()))).await?; D::close_connection(c).await?; Ok(Authenticated(user)) diff --git a/3/coursework/src/service/src/authentication/contract.rs b/3/coursework/src/service/src/authentication/contract.rs new file mode 100644 index 0000000..b226a68 --- /dev/null +++ b/3/coursework/src/service/src/authentication/contract.rs @@ -0,0 +1,122 @@ +use super::Authenticated; +use data::user; + +use derive_more::{Deref, Into}; +use garde::Validate; + +pub type Result = std::result::Result; + +pub trait AuthenticationContract { + fn name_available(&self, name: Name) -> impl Future + Send; + fn email_available(&self, email: Email) -> impl Future + Send; + + fn login(&self, data: LoginData) -> impl Future> + Send; + fn register( + &mut self, + data: RegisterData, + ) -> impl Future> + Send; +} + +pub struct LoginData { + pub login: Login, + pub password: Password, +} + +pub struct RegisterData { + pub name: Name, + pub email: Email, + pub password: Password, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + // Login + #[error("login was not found")] + LoginNotFound, + #[error("incorrect password")] + IncorrectPassword, + // Register + #[error("username is taken")] + NameExists, + #[error("email is already in use")] + EmailExists, + // Shared + #[error("invalid password: {0}")] + InvalidPassword(data::BoxDynError), + #[error("data source error: {0}")] + Repository(data::BoxDynError), +} + +#[derive(Clone)] +pub enum Login { + Name(Name), + Email(Email), +} +impl TryFrom for Login { + type Error = (String, &'static str); + + fn try_from(value: String) -> Result { + let value = match Email::try_from(value) { + Ok(x) => return Ok(Self::Email(x)), + Err((s, _)) => s, + }; + match Name::try_from(value) { + Ok(x) => Ok(Self::Name(x)), + Err((s, _)) => Err((s, "login is invalid")), + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Name(user::Name); +impl TryFrom for Name { + type Error = (String, Box); + + fn try_from(value: String) -> Result { + #[derive(Validate)] + #[garde(transparent)] + struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str); + + if let Err(e) = Username(&value).validate() { + return Err((value, e.into())); + } + match user::Name::try_from(value) { + Ok(x) => Ok(Self(x)), + Err((s, e)) => Err((s, e.into())), + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Email(user::Email); +impl TryFrom for Email { + type Error = (String, Box); + + fn try_from(value: String) -> Result { + #[derive(Validate)] + #[garde(transparent)] + pub struct Email<'a>(#[garde(email, length(chars, max = 255))] &'a str); + + if let Err(e) = Email(&value).validate() { + return Err((value, e.into())); + } + match user::Email::try_from(value) { + Ok(x) => Ok(Self(x)), + Err((s, e)) => Err((s, e.into())), + } + } +} + +#[derive(Clone, Deref, Into)] +pub struct Password(String); +impl TryFrom for Password { + type Error = (String, &'static str); + + fn try_from(value: String) -> Result { + if value.chars().count() >= 8 { + Ok(Self(value)) + } else { + Err((value, "password must be 8 characters or more")) + } + } +} diff --git a/3/coursework/src/service/src/authentication/repository.rs b/3/coursework/src/service/src/authentication/repository.rs new file mode 100644 index 0000000..aa7bca6 --- /dev/null +++ b/3/coursework/src/service/src/authentication/repository.rs @@ -0,0 +1,26 @@ +use data::Result; +use data::user::{Email, Name, New, Unique, User}; + +use derive_more::{Deref, DerefMut}; + +#[derive(Deref, DerefMut, Debug)] +pub struct Authenticated(pub(super) User); + +pub trait AuthenticationRepository { + fn get_user(&self, get: Get) -> impl Future>> + Send; + fn create_user(&self, new: New) -> impl Future> + Send; + fn start_session(&self, user: User) -> impl Future> + Send; +} + +pub enum Get { + Name(Name), + Email(Email), +} +impl From for Unique { + fn from(value: Get) -> Self { + match value { + Get::Name(s) => Self::Name(s), + Get::Email(s) => Self::Email(s), + } + } +} diff --git a/3/coursework/src/service/src/authentication/service.rs b/3/coursework/src/service/src/authentication/service.rs new file mode 100644 index 0000000..7df788d --- /dev/null +++ b/3/coursework/src/service/src/authentication/service.rs @@ -0,0 +1,114 @@ +use super::{ + Authenticated, AuthenticationContract, AuthenticationRepository, Email, Error, Get, Login, + LoginData, Name, RegisterData, Result, +}; + +use argon2::{ + Argon2, + password_hash::{ + self, PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng, + }, +}; + +impl From for Error { + fn from(error: password_hash::Error) -> Self { + match error { + password_hash::Error::Password => Self::IncorrectPassword, + _ => Self::InvalidPassword(error.into()), + } + } +} + +pub struct AuthenticationService +where + R: AuthenticationRepository, +{ + pub(crate) repository: R, +} + +impl AuthenticationService +where + R: AuthenticationRepository, +{ + pub const fn new(repository: R) -> Self { + Self { repository } + } +} + +impl AuthenticationContract for AuthenticationService +where + R: AuthenticationRepository + Send + Sync, +{ + async fn name_available(&self, name: Name) -> Result { + if self + .repository + .get_user(Get::Name(name.into())) + .await + .map_err(Error::Repository)? + .is_some() + { + return Err(Error::NameExists); + }; + Ok(()) + } + async fn email_available(&self, email: Email) -> Result { + if self + .repository + .get_user(Get::Email(email.into())) + .await + .map_err(Error::Repository)? + .is_some() + { + return Err(Error::EmailExists); + }; + Ok(()) + } + + async fn login(&self, data: LoginData) -> Result { + let user = match data.login { + Login::Name(name) => self.repository.get_user(Get::Name(name.into())), + Login::Email(email) => self.repository.get_user(Get::Email(email.into())), + } + .await + .map_err(Error::Repository)? + .ok_or(Error::LoginNotFound)?; + + Argon2::default().verify_password( + data.password.as_bytes(), + &PasswordHash::new(user.password())?, + )?; + + self.repository + .start_session(user) + .await + .map_err(Error::Repository) + } + + async fn register(&mut self, data: RegisterData) -> Result { + self.name_available(data.name.clone()).await?; + self.email_available(data.email.clone()).await?; + + // Get PHC string ($argon2id$v=19$...) + let password = Argon2::default() + .hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng))? + .to_string() + .try_into() + .map_err(|(_, e)| Error::InvalidPassword(Box::from(e)))?; + + let user = self + .repository + .create_user(data::user::New { + name: data.name.into(), + email: data.email.into(), + password, + last_used: None, + }) + .await + .map_err(Error::Repository)?; + + self.repository + .start_session(user) + .await + .map_err(Error::Repository) + } +} diff --git a/3/coursework/src/service/src/lib.rs b/3/coursework/src/service/src/lib.rs new file mode 100644 index 0000000..997a1fd --- /dev/null +++ b/3/coursework/src/service/src/lib.rs @@ -0,0 +1,6 @@ +pub mod authentication; + +pub use authentication::{ + Authenticated, AuthenticationAdapter, AuthenticationContract, AuthenticationRepository, + AuthenticationService, +}; diff --git a/3/coursework/src/view/Cargo.toml b/3/coursework/src/view/Cargo.toml new file mode 100644 index 0000000..b558b15 --- /dev/null +++ b/3/coursework/src/view/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "view" +version = "0.1.0" +edition = "2024" + +[dependencies] +iced = { version = "0.13.1", features = ["lazy", "tokio"] } diff --git a/3/coursework/src/view/src/authentication.rs b/3/coursework/src/view/src/authentication.rs new file mode 100644 index 0000000..f5685a3 --- /dev/null +++ b/3/coursework/src/view/src/authentication.rs @@ -0,0 +1,104 @@ +pub mod login; +pub mod register; + +use crate::input::Validation; + +use iced::Task; + +pub struct Authentication { + screen: Screen, + login: login::Login, + register: register::Register, +} +pub enum Screen { + Login, + Register, +} + +#[derive(Debug)] +pub enum Message { + Login(login::Message), + Register(register::Message), +} + +pub enum Request { + Task(Task), + SimpleLoginValidation(login::Field), + SimpleRegisterValidation(register::Field), + Login { + login: String, + password: String, + }, + Register { + name: String, + email: String, + password: String, + }, +} + +pub enum RequestResult { + Error(String), + LoginValidation(login::Field, Validation), + RegisterValidation(register::Field, Validation), +} + +impl Default for Authentication { + fn default() -> Self { + Self::new() + } +} + +impl Authentication { + pub const fn new() -> Self { + Self { + screen: Screen::Login, + login: Login::new(), + register: Register::new(), + } + } + + pub fn update(&mut self, message: Message) -> Option { + Some(match message { + Message::Login(message) => match self.login.update(message)? { + login::Request::SwitchToRegister => { + self.screen = Screen::Register; + return None; + } + login::Request::SimpleValidation(x) => Request::SimpleLoginValidation(x), + login::Request::Task(task) => Request::Task(task.map(Message::Login)), + login::Request::Login { login, password } => Request::Login { login, password }, + }, + Message::Register(message) => match self.register.update(message)? { + register::Request::SwitchToLogin => { + self.screen = Screen::Login; + return None; + } + register::Request::SimpleValidation(x) => Request::SimpleRegisterValidation(x), + register::Request::Task(task) => Request::Task(task.map(Message::Register)), + register::Request::Register { + name, + email, + password, + } => Request::Register { + name, + email, + password, + }, + }, + }) + } + + pub fn view(&self) -> iced::Element { + match self.screen { + Screen::Login => self.login.view().map(Message::Login), + Screen::Register => self.register.view().map(Message::Register), + } + } + + pub fn title(&self) -> std::borrow::Cow { + match self.screen { + Screen::Login => self.login.title(), + Screen::Register => self.register.title(), + } + } +} diff --git a/3/coursework/src/view/src/authentication/login.rs b/3/coursework/src/view/src/authentication/login.rs new file mode 100644 index 0000000..d0f2e3b --- /dev/null +++ b/3/coursework/src/view/src/authentication/login.rs @@ -0,0 +1,164 @@ +use crate::input::{Input, Validation}; +use crate::widget::centerbox; + +use iced::widget::{Space, button, checkbox, column, container, row, text}; +use iced::{Length, Task, padding}; + +pub struct Login { + state: State, + login: Input, + password: Input, + show_password: bool, +} +enum State { + None, + Requesting, + Error(String), +} + +pub enum Request { + SwitchToRegister, + SimpleValidation(Field), + Task(Task), + Login { login: String, password: String }, +} +pub enum Field { + Login(String), + Password(String), +} + +pub enum RequestResult { + Error(String), + Validation(Field, Validation), +} + +#[derive(Debug, Clone)] +pub enum Message { + LoginChanged(String), + PasswordChanged(String), + ShowPasswordToggled(bool), + + LoginSubmitted, + PasswordSubmitted, + + LoginPressed, + RegisterPressed, +} + +impl Default for Login { + fn default() -> Self { + Self::new() + } +} + +impl Login { + pub const fn new() -> Self { + Self { + state: State::None, + login: Input::new("login_name"), + password: Input::new("login_password"), + show_password: false, + } + } + + pub fn handle_result(&mut self, result: RequestResult) { + match result { + RequestResult::Error(e) => self.state = State::Error(e), + RequestResult::Validation(field, validation) => match &field { + Field::Login(s) => self.login.apply_if_eq(s, validation), + Field::Password(s) => self.password.apply_if_eq(s, validation), + }, + } + } + + pub fn update(&mut self, message: Message) -> Option { + Some(match message { + Message::LoginChanged(s) => { + self.login.update(s.clone()); + Request::SimpleValidation(Field::Login(s)) + } + Message::PasswordChanged(s) => { + self.password.update(s.clone()); + Request::SimpleValidation(Field::Password(s)) + } + + Message::ShowPasswordToggled(b) => { + self.show_password = b; + return None; + } + + Message::LoginSubmitted if !self.login.submittable() => return None, + Message::LoginSubmitted => Request::Task(self.login.focus()), + + Message::LoginPressed | Message::PasswordSubmitted => { + if !self.login.submittable() { + Request::Task(self.login.focus()) + } else if !self.password.submittable() { + Request::Task(self.password.focus()) + } else { + self.state = State::Requesting; + + Request::Login { + login: self.login.value().into(), + password: self.password.value().into(), + } + } + } + + Message::RegisterPressed => Request::SwitchToRegister, + }) + } + + pub fn view(&self) -> iced::Element { + centerbox( + column![ + container(text(self.title()).size(20)) + .center_x(Length::Fill) + .padding(padding::bottom(10)), + self.login + .view("Email or Username") + .on_input(Message::LoginChanged) + .on_submit(Message::LoginSubmitted), + self.password + .view("Password") + .on_input(Message::PasswordChanged) + .on_submit(Message::PasswordSubmitted) + .secure(!self.show_password), + checkbox("Show password", self.show_password) + .on_toggle(Message::ShowPasswordToggled), + row![ + button(text("Register").center().size(18)) + .on_press(Message::RegisterPressed) + .style(button::secondary) + .width(Length::FillPortion(3)) + .padding(10), + Space::with_width(Length::FillPortion(2)), + button(text("Login").center().size(18)) + .on_press(Message::LoginPressed) + .style(button::primary) + .width(Length::FillPortion(3)) + .padding(10) + ] + .padding(padding::top(15)), + ] + .width(Length::Fixed(250.)) + .spacing(20), + ) + } + + pub fn title(&self) -> std::borrow::Cow { + let errors = [ + self.login.error(), + self.password.error(), + self.login.warning(), + self.password.warning(), + ]; + let error = errors.into_iter().flatten().next(); + + match &self.state { + State::None => error.map_or_else(|| "Login".into(), Into::into), + State::Requesting => "Requesting...".into(), + State::Error(e) => e.into(), + } + } +} diff --git a/3/coursework/src/view/src/authentication/register.rs b/3/coursework/src/view/src/authentication/register.rs new file mode 100644 index 0000000..a3db851 --- /dev/null +++ b/3/coursework/src/view/src/authentication/register.rs @@ -0,0 +1,216 @@ +use crate::input::{Input, Validation}; +use crate::widget::centerbox; + +use iced::widget::{Space, button, checkbox, column, container, row, text}; +use iced::{Length, Task, padding}; + +pub struct Register { + state: State, + name: Input, + email: Input, + password: Input, + repeat: Input, + show_password: bool, +} +enum State { + None, + Requesting, + Error(String), +} + +pub enum Request { + SwitchToLogin, + SimpleValidation(Field), + Task(Task), + Register { + name: String, + email: String, + password: String, + }, +} +pub enum Field { + Name(String), + Email(String), + Password(String), +} + +pub enum RequestResult { + Error(String), + Validation(Field, Validation), +} + +#[derive(Debug, Clone)] +pub enum Message { + NameChanged(String), + EmailChanged(String), + PasswordChanged(String), + RepeatChanged(String), + ShowPasswordToggled(bool), + + EmailSubmitted, + NameSubmitted, + PasswordSubmitted, + RepeatSubmitted, + + RegisterPressed, + LoginPressed, +} + +impl Default for Register { + fn default() -> Self { + Self::new() + } +} + +impl Register { + pub const fn new() -> Self { + Self { + state: State::None, + name: Input::new("register_name"), + email: Input::new("register_email"), + password: Input::new("register_password"), + repeat: Input::new("register_repeat"), + show_password: false, + } + } + + pub fn handle_result(&mut self, result: RequestResult) { + match result { + RequestResult::Error(e) => self.state = State::Error(e), + RequestResult::Validation(field, validation) => match &field { + Field::Name(s) => self.name.apply_if_eq(s, validation), + Field::Email(s) => self.email.apply_if_eq(s, validation), + Field::Password(s) => self.password.apply_if_eq(s, validation), + }, + } + } + + #[inline] + fn check_passwords(&mut self) { + if self.password.value() != self.repeat.value() { + self.repeat.set_error("passwords are different".into()); + } + } + pub fn update(&mut self, message: Message) -> Option { + Some(match message { + Message::NameChanged(s) => { + self.name.update(s.clone()); + Request::SimpleValidation(Field::Name(s)) + } + Message::EmailChanged(s) => { + self.email.update(s.clone()); + Request::SimpleValidation(Field::Email(s)) + } + Message::PasswordChanged(s) => { + self.password.update(s.clone()); + self.check_passwords(); + Request::SimpleValidation(Field::Password(s)) + } + Message::RepeatChanged(s) => { + self.repeat.update(s); + self.check_passwords(); + return None; + } + + Message::ShowPasswordToggled(b) => { + self.show_password = b; + return None; + } + + Message::NameSubmitted if !self.name.submittable() => return None, + Message::NameSubmitted => Request::Task(self.email.focus()), + Message::EmailSubmitted if !self.email.submittable() => return None, + Message::EmailSubmitted => Request::Task(self.password.focus()), + Message::PasswordSubmitted if !self.password.submittable() => return None, + Message::PasswordSubmitted => Request::Task(self.repeat.focus()), + + Message::RegisterPressed | Message::RepeatSubmitted => { + if !self.name.submittable() { + Request::Task(self.name.focus()) + } else if !self.email.submittable() { + Request::Task(self.email.focus()) + } else if !self.password.submittable() { + Request::Task(self.password.focus()) + } else if !self.repeat.submittable() { + Request::Task(self.repeat.focus()) + } else { + self.state = State::Requesting; + + Request::Register { + name: self.name.value().into(), + email: self.email.value().into(), + password: self.password.value().into(), + } + } + } + + Message::LoginPressed => Request::SwitchToLogin, + }) + } + + pub fn view(&self) -> iced::Element { + centerbox( + column![ + container(text(self.title()).size(20)) + .center_x(Length::Fill) + .padding(padding::bottom(10)), + self.name + .view("Username") + .on_input(Message::NameChanged) + .on_submit(Message::NameSubmitted), + self.email + .view("Email") + .on_input(Message::EmailChanged) + .on_submit(Message::EmailSubmitted), + self.password + .view("Password") + .on_input(Message::PasswordChanged) + .on_submit(Message::PasswordSubmitted) + .secure(!self.show_password), + self.repeat + .view("Repeat Password") + .on_input(Message::RepeatChanged) + .on_submit(Message::RepeatSubmitted) + .secure(!self.show_password), + checkbox("Show password", self.show_password) + .on_toggle(Message::ShowPasswordToggled), + row![ + button(text("Login").center().size(18)) + .on_press(Message::LoginPressed) + .style(button::secondary) + .width(Length::FillPortion(3)) + .padding(10), + Space::with_width(Length::FillPortion(2)), + button(text("Register").center().size(18)) + .on_press(Message::RegisterPressed) + .style(button::primary) + .width(Length::FillPortion(3)) + .padding(10), + ] + .padding(padding::top(15)), + ] + .width(Length::Fixed(250.)) + .spacing(20), + ) + } + + pub fn title(&self) -> std::borrow::Cow { + let errors = [ + self.name.error(), + self.email.error(), + self.password.error(), + self.repeat.error(), + self.name.warning(), + self.email.warning(), + self.password.warning(), + self.repeat.warning(), + ]; + let error = errors.into_iter().flatten().next(); + + match &self.state { + State::None => error.map_or_else(|| "Register".into(), Into::into), + State::Requesting => "Requesting...".into(), + State::Error(e) => e.into(), + } + } +} diff --git a/3/coursework/src/view/src/input.rs b/3/coursework/src/view/src/input.rs new file mode 100644 index 0000000..5e8a70a --- /dev/null +++ b/3/coursework/src/view/src/input.rs @@ -0,0 +1,93 @@ +use crate::widget::text_input::{error, success, warning}; + +use iced::widget::{TextInput, text_input, text_input::default}; + +pub struct Input { + id: &'static str, + value: String, + warning: Option, + state: State, +} +enum State { + None, + Valid, + Invalid(String), +} + +pub enum Validation { + Valid, + Warning(String), + Invalid(String), +} + +impl Input { + pub const fn new(id: &'static str) -> Self { + Self { + id, + value: String::new(), + warning: None, + state: State::None, + } + } + + pub fn value(&self) -> &str { + self.value.as_ref() + } + pub fn error(&self) -> Option<&str> { + match &self.state { + State::Invalid(e) => Some(e.as_ref()), + _ => None, + } + } + pub fn warning(&self) -> Option<&str> { + self.warning.as_ref().map(AsRef::as_ref) + } + // pub fn submit(&self) -> Result { + // match &self.state { + // State::Invalid(e) => Err(e.as_ref()), + // State::None | State::Valid => Ok(self.value.clone()), + // } + // } + pub const fn submittable(&self) -> bool { + !matches!(self.state, State::Invalid(_)) + } + + pub fn update(&mut self, value: String) { + self.value = value; + self.warning = None; + self.state = State::None; + } + pub fn set_error(&mut self, value: String) { + self.state = State::Invalid(value); + } + pub fn set_warning(&mut self, value: String) { + self.warning = Some(value); + } + pub fn apply(&mut self, validation: Validation) { + match validation { + Validation::Valid => self.state = State::Valid, + Validation::Warning(w) => self.warning = Some(w), + Validation::Invalid(e) => self.state = State::Invalid(e), + } + } + pub fn apply_if_eq(&mut self, value: &str, validation: Validation) { + if self.value == value { + self.apply(validation); + } + } + + pub fn focus(&self) -> iced::Task { + iced::widget::text_input::focus(self.id) + } + pub fn view(&self, placeholder: &str) -> TextInput { + text_input(placeholder, &self.value) + .id(self.id) + .padding(12) + .style(match self.state { + State::None if self.warning.is_none() => default, + State::Valid if self.warning.is_none() => success, + State::Invalid(_) => error, + _ => warning, + }) + } +} diff --git a/3/coursework/src/view/src/lib.rs b/3/coursework/src/view/src/lib.rs new file mode 100644 index 0000000..df79b91 --- /dev/null +++ b/3/coursework/src/view/src/lib.rs @@ -0,0 +1,7 @@ +mod widget; + +mod input; +pub use input::Validation; + +pub mod authentication; +pub use authentication::{Authentication, login, register}; diff --git a/3/coursework/src/view/src/widget.rs b/3/coursework/src/view/src/widget.rs new file mode 100644 index 0000000..e9e3f7c --- /dev/null +++ b/3/coursework/src/view/src/widget.rs @@ -0,0 +1,71 @@ +use iced::widget::{Scrollable, center, container, mouse_area, scrollable, text, tooltip}; +use iced::{Element, color}; + +/// Put content into a dark container at the center of the screen +/// which can be scrolled in multiple dirrections +pub fn centerbox<'a, Message: 'a>( + content: impl Into>, +) -> Element<'a, Message> { + center(scroll( + container(content).style(container::dark).padding(20), + )) + .into() +} + +/// Scrollable but in both vertical and horizontal directions +pub fn scroll<'a, Message: 'a>(content: impl Into>) -> Element<'a, Message> { + Scrollable::with_direction(content, scrollable::Direction::Both { + vertical: scrollable::Scrollbar::default(), + horizontal: scrollable::Scrollbar::default(), + }) + .into() +} + +/// Clickable url +pub fn url<'a, Message: Clone + 'a>(txt: &impl ToString, msg: Message) -> Element<'a, Message> { + Element::from(mouse_area(text(txt.to_string()).color(color!(0xBB_B6_DF))).on_press(msg)) +} + +pub mod tip { + pub use iced::widget::tooltip::Position; +} + +/// Tooltip with some styling applied +pub fn tip<'a, Message: 'a>( + content: impl Into>, + tip: &'a str, + position: tip::Position, +) -> Element<'a, Message> { + tooltip( + content, + container(text(tip).size(14)) + .padding(5) + .style(container::dark), + position, + ) + .into() +} + +pub mod text_input { + use iced::widget::text_input::{Status, Style, default}; + use iced::{Theme, color}; + + pub fn success(theme: &Theme, status: Status) -> Style { + Style { + background: color!(0x00_33_00).into(), + ..default(theme, status) + } + } + pub fn warning(theme: &Theme, status: Status) -> Style { + Style { + background: color!(0x33_33_00).into(), + ..default(theme, status) + } + } + pub fn error(theme: &Theme, status: Status) -> Style { + Style { + background: color!(0x33_00_00).into(), + ..default(theme, status) + } + } +} From 9548279111d218e19ac91910dd13a5de11eb31f7 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Sat, 8 Feb 2025 16:35:07 +0200 Subject: [PATCH 08/13] Working Login & Register functionality --- 3/coursework/src/Cargo.lock | 217 ++++++++---------- 3/coursework/src/Cargo.toml | 13 +- 3/coursework/src/assets/init/init.sql | 50 ++-- ...655c21f03d04973e4291e6d4b6c7847364659.json | 12 + ...4a9cd92aaea3ea58ef8702903983cfc32ab47.json | 2 +- ...ad10878bb9aaa612f62080d40ba24682b0c3b.json | 12 + ...e6efb16919aff7172189c81240cb12462ae58.json | 2 +- ...8f9b092d74c2298ac126f7edbb7d59c536910.json | 12 - ...8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json | 12 - 3/coursework/src/data/Cargo.toml | 10 +- 3/coursework/src/data/src/adapter/mysql.rs | 1 + .../src/data/src/adapter/mysql/base.rs | 2 +- .../src/data/src/adapter/mysql/package.rs | 14 +- .../src/data/src/adapter/mysql/search.rs | 152 ++++++++++++ .../src/data/src/adapter/mysql/user.rs | 2 +- 3/coursework/src/data/src/lib.rs | 13 +- 3/coursework/src/data/src/port.rs | 57 ++++- 3/coursework/src/data/src/port/base.rs | 28 +-- 3/coursework/src/data/src/port/package.rs | 66 ++---- 3/coursework/src/data/src/port/search.rs | 69 ++++++ 3/coursework/src/data/src/port/user.rs | 45 +--- 3/coursework/src/service/Cargo.toml | 2 +- .../src/service/src/authentication/adapter.rs | 13 +- .../service/src/authentication/contract.rs | 79 +++++-- .../service/src/authentication/repository.rs | 2 +- .../src/service/src/authentication/service.rs | 14 +- 3/coursework/src/service/src/lib.rs | 2 + 3/coursework/src/src/input.rs | 171 ++++++++++++++ 3/coursework/src/src/login.rs | 177 ++++++++++++++ 3/coursework/src/src/main.rs | 184 +++++++++++++++ .../src/authentication => src}/register.rs | 189 ++++++++------- 3/coursework/src/{view => }/src/widget.rs | 11 +- 3/coursework/src/view/Cargo.toml | 7 - 3/coursework/src/view/src/authentication.rs | 104 --------- .../src/view/src/authentication/login.rs | 164 ------------- 3/coursework/src/view/src/input.rs | 93 -------- 3/coursework/src/view/src/lib.rs | 7 - 37 files changed, 1208 insertions(+), 802 deletions(-) create mode 100644 3/coursework/src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json create mode 100644 3/coursework/src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json delete mode 100644 3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json delete mode 100644 3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json create mode 100644 3/coursework/src/data/src/adapter/mysql/search.rs create mode 100644 3/coursework/src/data/src/port/search.rs create mode 100644 3/coursework/src/src/input.rs create mode 100644 3/coursework/src/src/login.rs create mode 100644 3/coursework/src/src/main.rs rename 3/coursework/src/{view/src/authentication => src}/register.rs (50%) rename 3/coursework/src/{view => }/src/widget.rs (89%) delete mode 100644 3/coursework/src/view/Cargo.toml delete mode 100644 3/coursework/src/view/src/authentication.rs delete mode 100644 3/coursework/src/view/src/authentication/login.rs delete mode 100644 3/coursework/src/view/src/input.rs delete mode 100644 3/coursework/src/view/src/lib.rs diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 8431ca4..696bd6a 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -273,7 +273,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -302,13 +302,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -464,7 +464,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -475,9 +475,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "calloop" @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.10" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "jobserver", "libc", @@ -905,6 +905,7 @@ version = "0.1.0" dependencies = [ "chrono", "derive_more", + "futures", "sqlx", ] @@ -927,22 +928,22 @@ dependencies = [ [[package]] name = "derive_more" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -997,7 +998,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -1105,7 +1106,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -1279,7 +1280,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -1378,7 +1379,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -1434,7 +1435,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -1764,7 +1765,7 @@ dependencies = [ "num-traits", "once_cell", "palette", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "smol_str", "thiserror 1.0.69", "web-time", @@ -1779,7 +1780,7 @@ dependencies = [ "futures", "iced_core", "log", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "tokio", "wasm-bindgen-futures", "wasm-timer", @@ -1794,7 +1795,7 @@ dependencies = [ "cosmic-text", "etagere", "lru", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "wgpu", ] @@ -1813,7 +1814,7 @@ dependencies = [ "log", "once_cell", "raw-window-handle", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "thiserror 1.0.69", "unicode-segmentation", ] @@ -1855,7 +1856,7 @@ dependencies = [ "iced_graphics", "kurbo", "log", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "softbuffer", "tiny-skia", ] @@ -1875,7 +1876,7 @@ dependencies = [ "iced_graphics", "log", "once_cell", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "thiserror 1.0.69", "wgpu", ] @@ -1891,7 +1892,7 @@ dependencies = [ "num-traits", "once_cell", "ouroboros", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "thiserror 1.0.69", "unicode-segmentation", ] @@ -1906,7 +1907,7 @@ dependencies = [ "iced_graphics", "iced_runtime", "log", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "thiserror 1.0.69", "tracing", "wasm-bindgen-futures", @@ -2031,7 +2032,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -2250,14 +2251,6 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -[[package]] -name = "main" -version = "0.1.0" -dependencies = [ - "iced", - "strum", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -2484,7 +2477,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -2720,9 +2713,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "orbclient" @@ -2774,7 +2767,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -2807,7 +2800,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -2926,7 +2919,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -2940,22 +2933,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -3077,7 +3070,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "version_check", "yansi", ] @@ -3090,9 +3083,9 @@ checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", ] @@ -3257,6 +3250,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "repo" +version = "0.1.0" +dependencies = [ + "data", + "iced", + "service", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -3307,9 +3309,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" @@ -3410,7 +3412,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -3433,7 +3435,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -3727,7 +3729,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -3750,7 +3752,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.96", + "syn 2.0.98", "tempfile", "tokio", "url", @@ -3890,28 +3892,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.96", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3948,9 +3928,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3965,7 +3945,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -4026,7 +4006,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -4037,7 +4017,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -4138,9 +4118,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ "indexmap", "toml_datetime", @@ -4167,7 +4147,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -4318,13 +4298,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "view" -version = "0.1.0" -dependencies = [ - "iced", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -4378,7 +4351,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "wasm-bindgen-shared", ] @@ -4413,7 +4386,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4444,9 +4417,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" dependencies = [ "cc", "downcast-rs", @@ -4458,9 +4431,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" dependencies = [ "bitflags 2.8.0", "rustix", @@ -4481,9 +4454,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" dependencies = [ "rustix", "wayland-client", @@ -4492,9 +4465,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.5" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ "bitflags 2.8.0", "wayland-backend", @@ -4504,9 +4477,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" dependencies = [ "bitflags 2.8.0", "wayland-backend", @@ -4517,9 +4490,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ "bitflags 2.8.0", "wayland-backend", @@ -4530,9 +4503,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", "quick-xml", @@ -4541,9 +4514,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ "dlib", "log", @@ -4974,9 +4947,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.8" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" +checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" dependencies = [ "ahash 0.8.11", "android-activity", @@ -5026,9 +4999,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.25" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] @@ -5168,7 +5141,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "synstructure", ] @@ -5219,7 +5192,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "zvariant_utils", ] @@ -5258,7 +5231,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -5278,7 +5251,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "synstructure", ] @@ -5307,7 +5280,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -5332,7 +5305,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", "zvariant_utils", ] @@ -5344,5 +5317,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml index 99ee3bb..3a63616 100644 --- a/3/coursework/src/Cargo.toml +++ b/3/coursework/src/Cargo.toml @@ -1,3 +1,14 @@ +[package] +name = "repo" +version = "0.1.0" +edition = "2024" + +[dependencies] +data = { path = "data" } +service = { path = "service" } + +iced = { version = "0.13.1", features = ["tokio", "lazy"] } + [workspace] resolver = "2" -members = ["data", "main", "service", "view"] +members = ["data", "service"] diff --git a/3/coursework/src/assets/init/init.sql b/3/coursework/src/assets/init/init.sql index 66dfbb0..6d1981a 100644 --- a/3/coursework/src/assets/init/init.sql +++ b/3/coursework/src/assets/init/init.sql @@ -3,7 +3,8 @@ CREATE DATABASE repository; USE repository; -- Required info for an account -CREATE TABLE Users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE Users ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, @@ -15,7 +16,8 @@ CREATE TABLE Users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, ); -- Enables multiple packages to have the same base yet different components -CREATE TABLE PackageBases ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE PackageBases ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(127) UNIQUE NOT NULL, description VARCHAR(510) NULL, @@ -24,27 +26,35 @@ CREATE TABLE PackageBases ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, ); -- User roles for working on packages: flagger, packager, submitter, maintainer, etc. -CREATE TABLE PackageBaseRoles ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE PackageBaseRoles ( + id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) UNIQUE NOT NULL, description VARCHAR(255) NULL ); +INSERT INTO PackageBaseRoles (id, name) VALUES +(1, 'submitter'), +(2, 'packager'), +(3, 'maintainer'), +(4, 'flagger'); + -- Roles that a user has for a package CREATE TABLE PackageBaseUserRoles ( - base_id INT UNSIGNED, - user_id INT UNSIGNED, - role_id TINYINT UNSIGNED, + base INT UNSIGNED, + user INT UNSIGNED, + role TINYINT UNSIGNED, comment VARCHAR(255) NULL, - PRIMARY KEY (base_id, user_id, role_id), -- composite key - FOREIGN KEY (base_id) REFERENCES PackageBases(id) ON DELETE CASCADE, - FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE, - FOREIGN KEY (role_id) REFERENCES PackageBaseRoles(id) ON DELETE CASCADE + PRIMARY KEY (base, user, role), -- composite key + FOREIGN KEY (base) REFERENCES PackageBases(id) ON DELETE CASCADE, + FOREIGN KEY (user) REFERENCES Users(id) ON DELETE CASCADE, + FOREIGN KEY (role) REFERENCES PackageBaseRoles(id) ON DELETE CASCADE ); -- Information about the actual packages -CREATE TABLE Packages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - package_base INT UNSIGNED NOT NULL, +CREATE TABLE Packages ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + base INT UNSIGNED NOT NULL, name VARCHAR(127) UNIQUE NOT NULL, version VARCHAR(127) NOT NULL, description VARCHAR(255) NULL, @@ -54,11 +64,12 @@ CREATE TABLE Packages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (package_base) REFERENCES PackageBases (id) ON DELETE CASCADE + FOREIGN KEY (base) REFERENCES PackageBases (id) ON DELETE CASCADE ); -- depends, makedepends, optdepends, etc. -CREATE TABLE DependencyTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE DependencyTypes ( + id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) UNIQUE NOT NULL ); INSERT INTO DependencyTypes (id, name) VALUES @@ -68,7 +79,8 @@ INSERT INTO DependencyTypes (id, name) VALUES (4, 'optdepends'); -- Track which dependencies a package has -CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE PackageDependencies ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, requirement VARCHAR(255) NULL, description VARCHAR(127) NULL, @@ -82,7 +94,8 @@ CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, ); -- conflicts, provides, replaces, etc. -CREATE TABLE RelationTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE RelationTypes ( + id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(31) UNIQUE NOT NULL ); INSERT INTO RelationTypes (id, name) VALUES @@ -91,9 +104,10 @@ INSERT INTO RelationTypes (id, name) VALUES (3, 'replaces'); -- Track which conflicts, provides and replaces a package has -CREATE TABLE PackageRelations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +CREATE TABLE PackageRelations ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, - requiremen VARCHAR(255) NULL, + requirement VARCHAR(255) NULL, package INT UNSIGNED NOT NULL, relation_type TINYINT UNSIGNED NOT NULL, diff --git a/3/coursework/src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json b/3/coursework/src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json new file mode 100644 index 0000000..9d27eba --- /dev/null +++ b/3/coursework/src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO Packages (base, name, version, description, url, flagged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 8 + }, + "nullable": [] + }, + "hash": "3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659" +} diff --git a/3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json b/3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json index 64fcfb8..07c6a04 100644 --- a/3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json +++ b/3/coursework/src/data/.sqlx/query-695f4b0a4286cf625dc60dc3dfc4a9cd92aaea3ea58ef8702903983cfc32ab47.json @@ -14,7 +14,7 @@ }, { "ordinal": 1, - "name": "package_base", + "name": "base", "type_info": { "type": "Long", "flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE", diff --git a/3/coursework/src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json b/3/coursework/src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json new file mode 100644 index 0000000..320b56f --- /dev/null +++ b/3/coursework/src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE Packages SET base = ? WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b" +} diff --git a/3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json b/3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json index 023fc05..336a2f1 100644 --- a/3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json +++ b/3/coursework/src/data/.sqlx/query-944eb40633e943a75244dee639fe6efb16919aff7172189c81240cb12462ae58.json @@ -14,7 +14,7 @@ }, { "ordinal": 1, - "name": "package_base", + "name": "base", "type_info": { "type": "Long", "flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE", diff --git a/3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json b/3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json deleted file mode 100644 index a432718..0000000 --- a/3/coursework/src/data/.sqlx/query-c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "MySQL", - "query": "UPDATE Packages SET package_base = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910" -} diff --git a/3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json b/3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json deleted file mode 100644 index 5c3f63a..0000000 --- a/3/coursework/src/data/.sqlx/query-dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "MySQL", - "query": "INSERT INTO Packages (package_base, name, version, description, url, flagged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 8 - }, - "nullable": [] - }, - "hash": "dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634" -} diff --git a/3/coursework/src/data/Cargo.toml b/3/coursework/src/data/Cargo.toml index 432c9e2..83a4d2c 100644 --- a/3/coursework/src/data/Cargo.toml +++ b/3/coursework/src/data/Cargo.toml @@ -4,18 +4,14 @@ version = "0.1.0" edition = "2024" [dependencies] -derive_more = { version = "1.0.0", features = ["deref", "into"] } +derive_more = { version = "2.0.1", features = ["deref", "into"] } +futures = "0.3.31" chrono = { version = "0.4.39", default-features = false, features = [ "std", "now", ] } -sqlx = { version = "0.8.3", default-features = false, features = [ - "mysql", - "macros", - "chrono", - "runtime-tokio", -] } +sqlx = { version = "0.8.3", default-features = false, features = ["mysql", "macros", "chrono", "runtime-tokio"] } # thiserror = "2.0.11" # garde = { version = "0.22.0", features = ["email", "url", "derive"] } diff --git a/3/coursework/src/data/src/adapter/mysql.rs b/3/coursework/src/data/src/adapter/mysql.rs index 9d6db43..67d439e 100644 --- a/3/coursework/src/data/src/adapter/mysql.rs +++ b/3/coursework/src/data/src/adapter/mysql.rs @@ -1,4 +1,5 @@ //! `MySQL` adapters. pub mod base; pub mod package; +pub mod search; pub mod user; diff --git a/3/coursework/src/data/src/adapter/mysql/base.rs b/3/coursework/src/data/src/adapter/mysql/base.rs index 6d9fa8d..51c0517 100644 --- a/3/coursework/src/data/src/adapter/mysql/base.rs +++ b/3/coursework/src/data/src/adapter/mysql/base.rs @@ -12,7 +12,7 @@ where for<'a> &'a E: Executor<'a, Database = MySql>, { } -impl crate::port::CRUD for BaseAdapter +impl crate::port::Crud for BaseAdapter where E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, diff --git a/3/coursework/src/data/src/adapter/mysql/package.rs b/3/coursework/src/data/src/adapter/mysql/package.rs index b1286d1..96dbf5e 100644 --- a/3/coursework/src/data/src/adapter/mysql/package.rs +++ b/3/coursework/src/data/src/adapter/mysql/package.rs @@ -12,7 +12,7 @@ where for<'a> &'a E: Executor<'a, Database = MySql>, { } -impl crate::port::CRUD for PackageAdapter +impl crate::port::Crud for PackageAdapter where E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, @@ -26,7 +26,7 @@ where let created_at = Utc::now(); let id = sqlx::query!( "INSERT INTO Packages \ - (package_base, name, version, description, url, flagged_at, created_at, updated_at) \ + (base, name, version, description, url, flagged_at, created_at, updated_at) \ VALUES (?, ?, ?, ?, ?, ?, ?, ?)", data.package_base.id, data.name.as_str(), @@ -43,7 +43,7 @@ where Ok(Self::Existing { id, - package_base: data.package_base.id, + base: data.package_base.id, name: data.name.into(), version: data.version.into(), description: data.description.into(), @@ -88,7 +88,7 @@ where } Field::PackageBase(package_base) => { sqlx::query!( - "UPDATE Packages SET package_base = ? WHERE id = ?", + "UPDATE Packages SET base = ? WHERE id = ?", package_base.id, existing.id ) @@ -107,7 +107,7 @@ where existing.id ) } - Field::URL(url) => { + Field::Url(url) => { sqlx::query!( "UPDATE Packages SET url = ? WHERE id = ?", url.as_ref(), @@ -135,10 +135,10 @@ where match data { Field::Name(s) => existing.name = s.into(), - Field::PackageBase(s) => existing.package_base = s.id, + Field::PackageBase(s) => existing.base = s.id, Field::Version(s) => existing.version = s.into(), Field::Description(o) => existing.description = o.into(), - Field::URL(o) => existing.url = o.into(), + Field::Url(o) => existing.url = o.into(), Field::FlaggedAt(date_time) => existing.flagged_at = date_time, Field::CreatedAt(date_time) => existing.created_at = date_time, Field::UpdatedAt(date_time) => existing.updated_at = date_time, diff --git a/3/coursework/src/data/src/adapter/mysql/search.rs b/3/coursework/src/data/src/adapter/mysql/search.rs new file mode 100644 index 0000000..684cbf0 --- /dev/null +++ b/3/coursework/src/data/src/adapter/mysql/search.rs @@ -0,0 +1,152 @@ +use crate::port::search::{Data, Entry, Mode, Order, SearchRepository}; +use crate::{Result, adapter::mysql::search}; + +// use chrono::Utc; +use futures::TryStreamExt; +use sqlx::{Executor, MySql, QueryBuilder, Row}; + +pub struct UserAdapter; + +impl SearchRepository for UserAdapter +where + E: Send, + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + async fn search(connection: &E, data: Data) -> Result> { + let mut builder = QueryBuilder::new( + "SELECT \ + p.id, p.name, p.version, p.url, p.description, \ + p.updated_at, p.created_at, \ + pb.id AS base_id, pb.name AS base_name, \ + ( \ + SELECT COUNT(DISTINCT pbur.user) \ + FROM PackageBaseUserRoles pbur \ + WHERE pbur.base = pb.id AND pbur.role = 3 \ + ) AS maintainers_num \ + FROM \ + Packages p \ + JOIN \ + PackageBases pb ON p.base = pb.id ", + ); + + let mut push_search = |cond, param| { + builder.push(format_args!( + " {cond} {param} {} ", + if data.exact { "=" } else { "LIKE" } + )); + builder.push_bind(if data.exact { + data.search.to_string() + } else { + format!("%{}%", data.search.as_str()) + }); + }; + + let join_user = " JOIN PackageBaseUserRoles pbur ON pb.id = pbur.base \ + JOIN Users u ON pbur.user = u.id WHERE "; + + match data.mode { + Mode::Url => push_search("WHERE", "p.url"), + Mode::Name => push_search("WHERE", "p.name"), + Mode::PackageBase => push_search("WHERE", "pb.name"), + Mode::Description => push_search("WHERE", "p.description"), + Mode::BaseDescription => push_search("WHERE", "pb.description"), + Mode::NameAndDescription => { + // WHERE (p.name LIKE '%search_term%' OR p.description LIKE '%search_term%') + builder.push(" WHERE p.name LIKE "); + builder.push_bind(format!("%{}%", data.search.as_str())); + builder.push(" OR p.description LIKE "); + builder.push_bind(format!("%{}%", data.search.as_str())); + } + Mode::User => { + push_search( + "WHERE EXISTS ( \ + SELECT 1 \ + FROM PackageBaseUserRoles pbur \ + JOIN Users u ON pbur.user = u.id \ + WHERE pbur.base = pb.id AND", + "u.name", + ); + builder.push(" ) "); + } + Mode::Flagger => { + push_search(join_user, "u.name"); + builder.push(" AND pbur.role = 4 "); + } // 4 + Mode::Packager => { + push_search(join_user, "u.name"); + builder.push(" AND pbur.role = 2 "); + } // 2 + Mode::Submitter => { + push_search(join_user, "u.name"); + builder.push(" AND pbur.role = 1 "); + } // 1 + Mode::Maintainer => { + push_search(join_user, "u.name"); + builder.push(" AND pbur.role = 3 "); + } // 3 + } + + builder.push(format_args!( + " ORDER BY {} {} LIMIT {};", + match data.order { + Order::Name => "p.name", + Order::Version => "p.version", + Order::BaseName => "pb.name", + Order::UpdatedAt => "p.updated_at", + Order::CreatedAt => "p.created_at", + }, + if data.ascending { "ASC" } else { "DESC" }, + data.limit + )); + + let mut entries = Vec::new(); + + let mut rows = builder.build().fetch(connection); + while let Some(row) = rows.try_next().await? { + entries.push(Entry { + id: row.try_get("id")?, + name: row.try_get("name")?, + version: row.try_get("version")?, + base_id: row.try_get("base_id")?, + base_name: row.try_get("base_name")?, + url: row.try_get("url")?, + description: row.try_get("description")?, + submitter_id: row.try_get("submitter_id")?, + submitter_name: row.try_get("submitter_name")?, + updated_at: row.try_get("updated_at")?, + created_at: row.try_get("created_at")?, + }); + } + + Ok(entries) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Validation; + use crate::port::search::Search; + use sqlx::MySqlPool; + + #[sqlx::test] + async fn search() -> crate::Result { + let pool = MySqlPool::connect_lazy( + &std::env::var("DATABASE_URL") + .expect("environment variable `DATABASE_URL` should be set"), + )?; + + let data = Data { + mode: Mode::NameAndDescription, + order: Order::UpdatedAt, + search: Search::new("f")?, + limit: 50, + exact: true, + ascending: false, + }; + + UserAdapter::search(&pool, data).await?; + + Ok(()) + } +} diff --git a/3/coursework/src/data/src/adapter/mysql/user.rs b/3/coursework/src/data/src/adapter/mysql/user.rs index ca66177..c7490ab 100644 --- a/3/coursework/src/data/src/adapter/mysql/user.rs +++ b/3/coursework/src/data/src/adapter/mysql/user.rs @@ -12,7 +12,7 @@ where for<'a> &'a E: Executor<'a, Database = MySql>, { } -impl crate::port::CRUD for UserAdapter +impl crate::port::Crud for UserAdapter where E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, diff --git a/3/coursework/src/data/src/lib.rs b/3/coursework/src/data/src/lib.rs index 8633dbc..ac3eebb 100644 --- a/3/coursework/src/data/src/lib.rs +++ b/3/coursework/src/data/src/lib.rs @@ -10,12 +10,13 @@ pub type Result = std::result::Result; pub use chrono::Utc; -pub use atomic::Atomic; -pub use connect::*; - pub use adapter::mysql::base::BaseAdapter as MySqlBaseAdapter; pub use adapter::mysql::package::PackageAdapter as MySqlPackageAdapter; pub use adapter::mysql::user::UserAdapter as MySqlUserAdapter; -pub use port::base::{self, Base, BaseRepository}; -pub use port::package::{self, Package, PackageRepository}; -pub use port::user::{self, User, UserRepository}; +pub use atomic::Atomic; +pub use connect::*; +pub use port::base::{Base, BaseRepository}; +pub use port::package::{Package, PackageRepository}; +pub use port::user::{User, UserRepository}; +pub use port::*; + diff --git a/3/coursework/src/data/src/port.rs b/3/coursework/src/data/src/port.rs index a9b70f4..e1d3dcb 100644 --- a/3/coursework/src/data/src/port.rs +++ b/3/coursework/src/data/src/port.rs @@ -1,13 +1,14 @@ //! Low-level repository traits for unified data access. //! -//! No data validation besides very basic one like length violation. -use crate::Result; +//! Very mild argument validation. +use crate::{BoxDynError, Result}; pub mod base; pub mod package; +pub mod search; pub mod user; -pub trait CRUD { +pub trait Crud { type New; type Unique; type Update; @@ -16,23 +17,27 @@ pub trait CRUD { fn create( connection: &mut C, data: Self::New, - ) -> impl Future> + Send; + ) -> impl Future> + Send; fn read( connection: &C, data: Self::Unique, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; fn update( connection: &mut C, existing: &mut Self::Existing, data: Self::Update, - ) -> impl Future + Send; - fn delete(connection: &mut C, data: Self::Unique) - -> impl Future + Send; + ) -> impl Future + Send; + fn delete(connection: &mut C, data: Self::Unique) -> impl Future + Send; } -trait CharLength { +pub trait CharLength { fn length(&self) -> usize; } +impl CharLength for &str { + fn length(&self) -> usize { + self.chars().count() + } +} impl CharLength for String { fn length(&self) -> usize { self.chars().count() @@ -44,15 +49,43 @@ impl CharLength for Option { } } -trait MaxLength { +trait Validatable { type Inner: CharLength; const MAX_LENGTH: usize; + fn encapsulate(value: Self::Inner) -> Self; +} - fn validate(value: &Self::Inner) -> Result<(), &'static str> { +#[allow(private_bounds)] // don't expose the impl details +pub trait Validation: Validatable +where + T: CharLength + Into, +{ + fn valid(value: &T) -> Result<(), String> { if value.length() > Self::MAX_LENGTH { - Err("too long") + Err(format!( + "too long (length: {}, max length: {})", + value.length(), + Self::MAX_LENGTH + )) } else { Ok(()) } } + + fn new(value: T) -> Result + where + Self: Sized, + { + match Self::valid(&value) { + Ok(()) => Ok(Self::encapsulate(value.into())), + Err(e) => Err((value, e.into())), + } + } +} + +impl Validation for T +where + T: Validatable, + U: CharLength + Into, +{ } diff --git a/3/coursework/src/data/src/port/base.rs b/3/coursework/src/data/src/port/base.rs index d7c72fe..a6e3a96 100644 --- a/3/coursework/src/data/src/port/base.rs +++ b/3/coursework/src/data/src/port/base.rs @@ -1,10 +1,10 @@ -use super::MaxLength; +use super::Validatable; use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait BaseRepository: - super::CRUD + super::Crud { } @@ -13,33 +13,21 @@ pub trait BaseRepository: #[derive(Clone, Deref, Into)] pub struct Name(String); -impl MaxLength for Name { +impl Validatable for Name { type Inner = String; const MAX_LENGTH: usize = 127; -} -impl TryFrom for Name { - type Error = &'static str; - - fn try_from(value: String) -> Result { - Self::validate(&value)?; - Ok(Self(value)) + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] pub struct Description(Option); -impl MaxLength for Description { +impl Validatable for Description { type Inner = Option; const MAX_LENGTH: usize = 510; -} -impl TryFrom> for Description { - type Error = (Option, &'static str); - - fn try_from(value: Option) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } diff --git a/3/coursework/src/data/src/port/package.rs b/3/coursework/src/data/src/port/package.rs index 6d7862e..2f35392 100644 --- a/3/coursework/src/data/src/port/package.rs +++ b/3/coursework/src/data/src/port/package.rs @@ -1,79 +1,51 @@ -use super::MaxLength; +use super::Validatable; use crate::Base; use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait PackageRepository: - super::CRUD + super::Crud { } #[derive(Clone, Deref, Into)] pub struct Name(String); -impl MaxLength for Name { +impl Validatable for Name { type Inner = String; const MAX_LENGTH: usize = 127; -} -impl TryFrom for Name { - type Error = (String, &'static str); - - fn try_from(value: String) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] pub struct Version(String); -impl MaxLength for Version { +impl Validatable for Version { type Inner = String; const MAX_LENGTH: usize = 127; -} -impl TryFrom for Version { - type Error = (String, &'static str); - - fn try_from(value: String) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] pub struct Description(Option); -impl MaxLength for Description { +impl Validatable for Description { type Inner = Option; const MAX_LENGTH: usize = 255; -} -impl TryFrom> for Description { - type Error = (Option, &'static str); - - fn try_from(value: Option) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] -pub struct URL(Option); -impl MaxLength for URL { +pub struct Url(Option); +impl Validatable for Url { type Inner = Option; const MAX_LENGTH: usize = 510; -} -impl TryFrom> for URL { - type Error = (Option, &'static str); - - fn try_from(value: Option) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } @@ -87,7 +59,7 @@ pub enum Field { Name(Name), Version(Version), Description(Description), - URL(URL), + Url(Url), FlaggedAt(Option>), CreatedAt(DateTime), UpdatedAt(DateTime), @@ -98,13 +70,13 @@ pub struct New { pub name: Name, pub version: Version, pub description: Description, - pub url: URL, + pub url: Url, pub flagged_at: Option>, } pub struct Package { pub(crate) id: u64, - pub(crate) package_base: u64, + pub(crate) base: u64, pub(crate) name: String, pub(crate) version: String, pub(crate) description: Option, @@ -119,7 +91,7 @@ impl Package { self.id } pub const fn package_base(&self) -> u64 { - self.package_base + self.base } pub const fn name(&self) -> &String { &self.name diff --git a/3/coursework/src/data/src/port/search.rs b/3/coursework/src/data/src/port/search.rs new file mode 100644 index 0000000..0475992 --- /dev/null +++ b/3/coursework/src/data/src/port/search.rs @@ -0,0 +1,69 @@ +use super::Validatable; +use crate::Result; + +use chrono::{DateTime, Utc}; +use derive_more::{Deref, Into}; + +pub trait SearchRepository { + fn search(connection: &C, data: Data) -> impl Future>> + Send; +} + +#[derive(Clone, Deref, Into)] +pub struct Search(String); +impl Validatable for Search { + type Inner = String; + const MAX_LENGTH: usize = 255; + fn encapsulate(value: Self::Inner) -> Self { + Self(value) + } +} + +pub struct Data { + pub mode: Mode, + pub order: Order, + pub search: Search, + + pub limit: u8, + pub exact: bool, + pub ascending: bool, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Entry { + pub id: u64, + pub name: Box, + pub version: Box, + pub base_id: u64, + pub base_name: Box, + pub url: Option>, + pub description: Box, + pub submitter_id: u64, + pub submitter_name: Box, + pub updated_at: DateTime, + pub created_at: DateTime, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Url, + Name, + PackageBase, + Description, + BaseDescription, + NameAndDescription, + User, + Flagger, + Packager, + Submitter, + Maintainer, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Order { + Name, + Version, + BaseName, + // Submitter, + UpdatedAt, + CreatedAt, +} diff --git a/3/coursework/src/data/src/port/user.rs b/3/coursework/src/data/src/port/user.rs index 359ce02..f1085a0 100644 --- a/3/coursework/src/data/src/port/user.rs +++ b/3/coursework/src/data/src/port/user.rs @@ -1,61 +1,40 @@ -use super::MaxLength; +use super::Validatable; use chrono::{DateTime, Utc}; use derive_more::{Deref, Into}; pub trait UserRepository: - super::CRUD + super::Crud { } #[derive(Clone, Deref, Into)] pub struct Name(String); -impl MaxLength for Name { +impl Validatable for Name { type Inner = String; const MAX_LENGTH: usize = 31; -} -impl TryFrom for Name { - type Error = (String, &'static str); - - fn try_from(value: String) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] pub struct Email(String); -impl MaxLength for Email { +impl Validatable for Email { type Inner = String; const MAX_LENGTH: usize = 255; -} -impl TryFrom for Email { - type Error = (String, &'static str); - - fn try_from(value: String) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } #[derive(Clone, Deref, Into)] pub struct Password(String); -impl MaxLength for Password { +impl Validatable for Password { type Inner = String; const MAX_LENGTH: usize = 255; -} -impl TryFrom for Password { - type Error = (String, &'static str); - - fn try_from(value: String) -> Result { - match Self::validate(&value) { - Ok(()) => Ok(Self(value)), - Err(e) => Err((value, e)), - } + fn encapsulate(value: Self::Inner) -> Self { + Self(value) } } @@ -81,7 +60,7 @@ pub struct New { pub last_used: Option>, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct User { pub(crate) id: u64, pub(crate) name: String, diff --git a/3/coursework/src/service/Cargo.toml b/3/coursework/src/service/Cargo.toml index 372db4c..c7270b6 100644 --- a/3/coursework/src/service/Cargo.toml +++ b/3/coursework/src/service/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" thiserror = "2.0.11" argon2 = { version = "0.5.3", features = ["std"] } garde = { version = "0.22.0", features = ["email", "url", "derive"] } -derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "into"] } +derive_more = { version = "2.0.1", features = ["deref", "deref_mut", "into"] } [dependencies.data] path = "../data" diff --git a/3/coursework/src/service/src/authentication/adapter.rs b/3/coursework/src/service/src/authentication/adapter.rs index 76173de..98ac5d0 100644 --- a/3/coursework/src/service/src/authentication/adapter.rs +++ b/3/coursework/src/service/src/authentication/adapter.rs @@ -11,6 +11,7 @@ where UR: UserRepository + Sync, { driver: D, + // connection: Option, _user_repository: PhantomData, } @@ -23,14 +24,24 @@ where pub const fn new(driver: D) -> Self { Self { driver, + // connection: None, _user_repository: PhantomData, } } + + // async fn connection(&mut self) -> Result<&C> { + // if let Some(ref c) = self.connection { + // Ok(c) + // } else { + // self.connection = Some(self.driver.open_connection().await?); + // self.connection().await + // } + // } } impl AuthenticationRepository for AuthenticationAdapter where - C: Send, + C: Send, //+ Sync, D: Connect + Sync, UR: UserRepository + Sync, { diff --git a/3/coursework/src/service/src/authentication/contract.rs b/3/coursework/src/service/src/authentication/contract.rs index b226a68..0d80243 100644 --- a/3/coursework/src/service/src/authentication/contract.rs +++ b/3/coursework/src/service/src/authentication/contract.rs @@ -1,12 +1,13 @@ use super::Authenticated; -use data::user; +pub use data::Validation; +use data::{BoxDynError, user}; use derive_more::{Deref, Into}; use garde::Validate; pub type Result = std::result::Result; -pub trait AuthenticationContract { +pub trait AuthenticationContract: Send { fn name_available(&self, name: Name) -> impl Future + Send; fn email_available(&self, email: Email) -> impl Future + Send; @@ -45,78 +46,110 @@ pub enum Error { InvalidPassword(data::BoxDynError), #[error("data source error: {0}")] Repository(data::BoxDynError), + + #[error(transparent)] + Other(data::BoxDynError), } +pub type ReturnError = (T, BoxDynError); + #[derive(Clone)] pub enum Login { Name(Name), Email(Email), } +impl AsRef for Login { + fn as_ref(&self) -> &str { + match self { + Self::Name(name) => name.as_ref(), + Self::Email(email) => email.as_ref(), + } + } +} impl TryFrom for Login { - type Error = (String, &'static str); + type Error = ReturnError; fn try_from(value: String) -> Result { let value = match Email::try_from(value) { Ok(x) => return Ok(Self::Email(x)), - Err((s, _)) => s, + Err((v, _)) => v, }; match Name::try_from(value) { Ok(x) => Ok(Self::Name(x)), - Err((s, _)) => Err((s, "login is invalid")), + Err((v, _)) => Err((v, "login is invalid".into())), + } + } +} +impl From for String { + fn from(val: Login) -> Self { + match val { + Login::Name(name) => name.0.into(), + Login::Email(email) => email.0.into(), } } } #[derive(Clone, Deref, Into)] pub struct Name(user::Name); +impl AsRef for Name { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} impl TryFrom for Name { - type Error = (String, Box); + type Error = ReturnError; fn try_from(value: String) -> Result { #[derive(Validate)] #[garde(transparent)] - struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str); + struct Username<'a>(#[garde(ascii, length(chars, min = 2, max = 31))] &'a str); - if let Err(e) = Username(&value).validate() { - return Err((value, e.into())); - } - match user::Name::try_from(value) { - Ok(x) => Ok(Self(x)), - Err((s, e)) => Err((s, e.into())), + match Username(value.as_str()).validate() { + Ok(()) => (), + Err(e) => return Err((value, e.into())), } + Ok(Self(user::Name::new(value)?)) } } #[derive(Clone, Deref, Into)] pub struct Email(user::Email); +impl AsRef for Email { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} impl TryFrom for Email { - type Error = (String, Box); + type Error = ReturnError; fn try_from(value: String) -> Result { #[derive(Validate)] #[garde(transparent)] pub struct Email<'a>(#[garde(email, length(chars, max = 255))] &'a str); - if let Err(e) = Email(&value).validate() { - return Err((value, e.into())); - } - match user::Email::try_from(value) { - Ok(x) => Ok(Self(x)), - Err((s, e)) => Err((s, e.into())), + match Email(value.as_str()).validate() { + Ok(()) => (), + Err(e) => return Err((value, e.into())), } + Ok(Self(user::Email::new(value)?)) } } #[derive(Clone, Deref, Into)] pub struct Password(String); +impl AsRef for Password { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} impl TryFrom for Password { - type Error = (String, &'static str); + type Error = ReturnError; fn try_from(value: String) -> Result { - if value.chars().count() >= 8 { + if value.chars().count() > 7 { Ok(Self(value)) } else { - Err((value, "password must be 8 characters or more")) + Err((value, "password must be longer than 7 characters".into())) } } } diff --git a/3/coursework/src/service/src/authentication/repository.rs b/3/coursework/src/service/src/authentication/repository.rs index aa7bca6..39eaefa 100644 --- a/3/coursework/src/service/src/authentication/repository.rs +++ b/3/coursework/src/service/src/authentication/repository.rs @@ -3,7 +3,7 @@ use data::user::{Email, Name, New, Unique, User}; use derive_more::{Deref, DerefMut}; -#[derive(Deref, DerefMut, Debug)] +#[derive(Debug, Clone, Deref, DerefMut)] pub struct Authenticated(pub(super) User); pub trait AuthenticationRepository { diff --git a/3/coursework/src/service/src/authentication/service.rs b/3/coursework/src/service/src/authentication/service.rs index 7df788d..e044d3a 100644 --- a/3/coursework/src/service/src/authentication/service.rs +++ b/3/coursework/src/service/src/authentication/service.rs @@ -1,6 +1,6 @@ use super::{ Authenticated, AuthenticationContract, AuthenticationRepository, Email, Error, Get, Login, - LoginData, Name, RegisterData, Result, + LoginData, Name, RegisterData, Result, Validation, }; use argon2::{ @@ -48,7 +48,7 @@ where .is_some() { return Err(Error::NameExists); - }; + } Ok(()) } async fn email_available(&self, email: Email) -> Result { @@ -60,7 +60,7 @@ where .is_some() { return Err(Error::EmailExists); - }; + } Ok(()) } @@ -89,11 +89,11 @@ where self.email_available(data.email.clone()).await?; // Get PHC string ($argon2id$v=19$...) - let password = Argon2::default() + let phc = Argon2::default() .hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng))? - .to_string() - .try_into() - .map_err(|(_, e)| Error::InvalidPassword(Box::from(e)))?; + .to_string(); + let password = data::user::Password::new(phc) + .map_err(|(_, e)| Error::InvalidPassword(e))?; let user = self .repository diff --git a/3/coursework/src/service/src/lib.rs b/3/coursework/src/service/src/lib.rs index 997a1fd..9111453 100644 --- a/3/coursework/src/service/src/lib.rs +++ b/3/coursework/src/service/src/lib.rs @@ -4,3 +4,5 @@ pub use authentication::{ Authenticated, AuthenticationAdapter, AuthenticationContract, AuthenticationRepository, AuthenticationService, }; + +// pub diff --git a/3/coursework/src/src/input.rs b/3/coursework/src/src/input.rs new file mode 100644 index 0000000..d50ebfd --- /dev/null +++ b/3/coursework/src/src/input.rs @@ -0,0 +1,171 @@ +use crate::widget::text_input::{error, success, warning}; + +use iced::widget::{TextInput, text_input, text_input::default}; + +/// A smarter [`text_input`].to avoid boilerplate. +pub struct Input { + id: &'static str, + value: Value, + warning: Option, +} + +// use std::ops::Deref; +// impl Deref for Input { +// type Target = Value; +// fn deref(&self) -> &Self::Target { +// &self.value +// } +// } + +impl> AsRef for Input { + fn as_ref(&self) -> &str { + self.value.as_ref() + } +} + +impl Input { + pub const fn new(id: &'static str) -> Self { + Self { + id, + value: Value::None, + warning: None, + } + } + + pub fn focus(&self) -> iced::Task { + iced::widget::text_input::focus(self.id) + } + + pub fn warning(&self) -> Option<&str> { + self.warning.as_ref().map(AsRef::as_ref) + } + pub fn error(&self) -> Option<&str> { + match &self.value { + Value::Invalid { error, .. } => Some(error.as_ref()), + _ => None, + } + } + + pub fn set_value(&mut self, value: Value) { + self.value = value; + } + pub fn set_warning(&mut self, value: &impl ToString) { + self.warning = Some(value.to_string()); + } +} + +impl> Input { + pub fn set_error(&mut self, value: &impl ToString) { + self.value.set_error(value.to_string()); + } + + pub fn view(&self, placeholder: &str) -> TextInput { + text_input(placeholder, self.value.as_ref()) + .id(self.id) + .padding(12) + .style(match self.value { + Value::Invalid { .. } => error, + Value::None | Value::Valid(_) if self.warning.is_some() => warning, + Value::Valid(_) => success, + Value::None => default, + }) + } +} + +impl Input +where + E: ToString, + T: TryFrom, +{ + pub fn update(&mut self, value: String) { + self.value.update(value); + self.warning = None; + } + + pub fn value(&mut self) -> Result<&T, &str> { + match self.value { + Value::None => { + self.value.update(String::new()); + self.value() + } + Value::Valid(ref x) => Ok(x), + Value::Invalid { ref error, .. } => Err(error), + } + } + pub fn submittable(&mut self) -> bool { + self.value().is_ok() + } + pub fn critical(&mut self) -> bool { + self.value().is_err() + } +} + +impl Input +where + E: ToString, + T: TryFrom + Clone, +{ + pub fn submit(&mut self) -> Result> { + match self.value() { + Ok(x) => Ok(x.clone()), + Err(_) => Err(self.focus()), + } + } +} + +#[derive(Default)] +pub enum Value { + #[default] + None, + Valid(T), + Invalid { + value: String, + error: String, + }, +} + +impl> AsRef for Value { + fn as_ref(&self) -> &str { + match self { + Self::None => "", + Self::Valid(x) => x.as_ref(), + Self::Invalid { value, .. } => value.as_ref(), + } + } +} + +impl> Value { + fn set_error(&mut self, error: String) { + match self { + Self::None => { + *self = Self::Invalid { + value: String::new(), + error, + } + } + Self::Valid(x) => { + *self = Self::Invalid { + value: x.as_ref().to_string(), + error, + } + } + Self::Invalid { error: e, .. } => *e = error, + } + } +} + +impl Value +where + E: ToString, + T: TryFrom, +{ + fn update(&mut self, value: String) { + *self = match T::try_from(value) { + Ok(x) => Self::Valid(x), + Err((s, e)) => Self::Invalid { + value: s, + error: e.to_string(), + }, + }; + } +} diff --git a/3/coursework/src/src/login.rs b/3/coursework/src/src/login.rs new file mode 100644 index 0000000..880db49 --- /dev/null +++ b/3/coursework/src/src/login.rs @@ -0,0 +1,177 @@ +use crate::input::Input; +use crate::widget::centerbox; +use service::authentication; +use service::{ + Authenticated, AuthenticationContract, + authentication::{Error, LoginData, Result}, +}; + +use iced::futures::lock::Mutex; +use iced::widget::{Space, button, checkbox, column, container, row, text}; +use iced::{Length, Task, padding}; +use std::sync::Arc; + +pub struct Login { + login: Input, + password: Input, + show_password: bool, + + state: State, + service: Arc>, +} +enum State { + None, + Requesting, + Error(String), +} + +pub enum Event { + SwitchToRegister, + Task(Task), + Authenticated(Authenticated), +} +impl From> for Event { + fn from(value: Task) -> Self { + Self::Task(value) + } +} + +#[derive(Debug, Clone)] +pub enum Message { + LoginChanged(String), + PasswordChanged(String), + ShowPasswordToggled(bool), + + LoginSubmitted, + PasswordSubmitted, + + LoginPressed, + RegisterPressed, + + RequestResult(Arc>), +} + +impl Login { + pub fn new(service: Arc>) -> Self { + Self { + login: Input::new("login_name"), + password: Input::new("login_password"), + show_password: false, + state: State::None, + service, + } + } + + pub fn update(&mut self, message: Message) -> Option { + match message { + Message::LoginChanged(s) => self.login.update(s), + Message::PasswordChanged(s) => self.password.update(s), + Message::ShowPasswordToggled(b) => self.show_password = b, + + Message::LoginSubmitted if self.login.critical() => (), + Message::LoginSubmitted => return Some(self.password.focus().into()), + + Message::RegisterPressed => return Some(Event::SwitchToRegister), + + Message::LoginPressed | Message::PasswordSubmitted => { + let login_data = LoginData { + login: match self.login.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + password: match self.password.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + }; + + self.state = State::Requesting; + let arc = self.service.clone(); + + return Some( + Task::perform( + async move { + let Some(service) = arc.try_lock() else { + return Err(Error::Other( + "other authentication request is being performed".into(), + )); + }; + service.login(login_data).await + }, + |r| Message::RequestResult(Arc::new(r)), + ) + .into(), + ); + } + Message::RequestResult(r) => match &*r { + Ok(a) => return Some(Event::Authenticated(a.clone())), + + Err(e) => { + self.state = State::None; + match e { + Error::LoginNotFound => self.login.set_warning(e), + Error::IncorrectPassword => self.password.set_warning(e), + Error::InvalidPassword(_) => self.password.set_error(e), + + _ => self.state = State::Error(e.to_string()), + } + } + }, + } + + None + } + + pub fn view(&self) -> iced::Element { + centerbox( + column![ + container(text(self.title()).size(20)) + .center_x(Length::Fill) + .padding(padding::bottom(10)), + self.login + .view("Email or Username") + .on_input(Message::LoginChanged) + .on_submit(Message::LoginSubmitted), + self.password + .view("Password") + .on_input(Message::PasswordChanged) + .on_submit(Message::PasswordSubmitted) + .secure(!self.show_password), + checkbox("Show password", self.show_password) + .on_toggle(Message::ShowPasswordToggled), + row![ + button(text("Register").center().size(18)) + .on_press(Message::RegisterPressed) + .style(button::secondary) + .width(Length::FillPortion(3)) + .padding(10), + Space::with_width(Length::FillPortion(2)), + button(text("Login").center().size(18)) + .on_press(Message::LoginPressed) + .style(button::primary) + .width(Length::FillPortion(3)) + .padding(10) + ] + .padding(padding::top(15)), + ] + .width(Length::Fixed(250.)) + .spacing(20), + ) + } + + pub fn title(&self) -> String { + let errors = [ + self.login.error(), + self.password.error(), + self.login.warning(), + self.password.warning(), + ]; + let error = errors.into_iter().flatten().next(); + + match &self.state { + State::None => error.map_or_else(|| "Login".into(), Into::into), + State::Requesting => "Requesting...".into(), + State::Error(e) => e.into(), + } + } +} diff --git a/3/coursework/src/src/main.rs b/3/coursework/src/src/main.rs new file mode 100644 index 0000000..7355706 --- /dev/null +++ b/3/coursework/src/src/main.rs @@ -0,0 +1,184 @@ +// mod main_window; +// mod authentication; +mod input; +mod login; +mod register; +mod widget; + +use std::sync::Arc; + +use crate::login::Login; +use crate::register::Register; +// use crate::authentication::Authentication; +// use crate::main_window::MainWindow; + +use data::{MySqlPool, MySqlUserAdapter, SqlxPool}; +use iced::{ + Element, Subscription, Task, Theme, + futures::lock::Mutex, + widget::{center, row}, + window, +}; +use service::{AuthenticationAdapter, AuthenticationService}; + +// #[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + #[cfg(debug_assertions)] + println!($($arg)*) + }; +} + +fn main() -> iced::Result { + iced::daemon(Repository::title, Repository::update, Repository::view) + .subscription(Repository::subscription) + .scale_factor(Repository::scale_factor) + .theme(Repository::theme) + .run_with(Repository::new) +} + +struct Repository { + scale_factor: f64, + main_id: window::Id, + login: + Login>>, + register: Register< + AuthenticationService>, + >, + screen: Screen, + // authentication: Authentication, +} + +enum Screen { + Login, + Register, +} + +#[derive(Debug)] +enum Message { + ScaleUp, + ScaleDown, + WindowOpened(window::Id), + WindowClosed(window::Id), + + Login(login::Message), + Register(register::Message), + // MainWindow(main_window::Message), +} + +impl Repository { + fn new() -> (Self, Task) { + let (main_id, open_task) = window::open(window::Settings::default()); + // let (main_window, main_window_task) = MainWindow::new(); + + let pool = MySqlPool::new( + SqlxPool::connect_lazy( + &std::env::var("DATABASE_URL") + .expect("environment variable `DATABASE_URL` should be set"), + ) + .unwrap(), + ); + + let auth_service = Arc::new(Mutex::new(AuthenticationService::new( + AuthenticationAdapter::new(pool.clone()), + ))); + + ( + Self { + scale_factor: 1.4, + main_id, + login: Login::new(auth_service.clone()), + register: Register::new(auth_service), + screen: Screen::Login, + }, + Task::batch([ + open_task.map(Message::WindowOpened), + // main_window_task.map(Message::MainWindow), + ]), + ) + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::ScaleUp => self.scale_factor = (self.scale_factor + 0.2).min(5.0), + Message::ScaleDown => self.scale_factor = (self.scale_factor - 0.2).max(0.2), + Message::WindowOpened(id) => { + log!("Window opened: {id}"); + return iced::widget::focus_next(); + } + Message::WindowClosed(id) => { + log!("Window closed: {id}"); + if id == self.main_id { + return iced::exit(); + } + } + Message::Login(message) => { + if let Some(action) = self.login.update(message) { + match action { + login::Event::SwitchToRegister => self.screen = Screen::Register, + login::Event::Task(task) => return task.map(Message::Login), + login::Event::Authenticated(authenticated) => { + log!("authenticated via login {:#?}", authenticated); + } + } + } + } + Message::Register(message) => { + if let Some(action) = self.register.update(message) { + match action { + register::Event::SwitchToLogin => self.screen = Screen::Login, + register::Event::Task(task) => return task.map(Message::Register), + register::Event::Authenticated(authenticated) => { + log!("authenticated via register: {:#?}", authenticated); + } + } + } + } // + // Message::MainWindow(message) => match self.main_window.update(message) { + // main_window::Action::None => (), + // main_window::Action::Task(task) => return task.map(Message::MainWindow), + // }, + } + Task::none() + } + + fn view(&self, id: window::Id) -> Element { + if self.main_id == id { + // self.main_window.view().map(Message::MainWindow) + match self.screen { + Screen::Login => self.login.view().map(Message::Login), + Screen::Register => self.register.view().map(Message::Register), + } + } else { + center(row!["This window is unknown.", "It may be closed."]).into() + } + } + + fn title(&self, _: window::Id) -> String { + // "Repository".into() + match self.screen { + Screen::Login => self.login.title(), + Screen::Register => self.register.title(), + } + } + + fn subscription(&self) -> Subscription { + use iced::keyboard::{self, Key, Modifiers}; + + let hotkeys = keyboard::on_key_press(|key, modifiers| match (modifiers, key) { + (Modifiers::CTRL, Key::Character(c)) if c == "-" => Some(Message::ScaleDown), + (Modifiers::CTRL, Key::Character(c)) if c == "=" || c == "+" => Some(Message::ScaleUp), + _ => None, + }); + + Subscription::batch([hotkeys, window::close_events().map(Message::WindowClosed)]) + } + + const fn scale_factor(&self, _: window::Id) -> f64 { + self.scale_factor + } + + const fn theme(_: &Self, _: window::Id) -> Theme { + Theme::TokyoNight + } +} diff --git a/3/coursework/src/view/src/authentication/register.rs b/3/coursework/src/src/register.rs similarity index 50% rename from 3/coursework/src/view/src/authentication/register.rs rename to 3/coursework/src/src/register.rs index a3db851..e62876c 100644 --- a/3/coursework/src/view/src/authentication/register.rs +++ b/3/coursework/src/src/register.rs @@ -1,16 +1,25 @@ -use crate::input::{Input, Validation}; +use crate::input::Input; use crate::widget::centerbox; +use service::authentication::{self, Email, Name, Password, RegisterData}; +use service::{ + Authenticated, AuthenticationContract, + authentication::{Error, LoginData, Result}, +}; +use iced::futures::lock::Mutex; use iced::widget::{Space, button, checkbox, column, container, row, text}; use iced::{Length, Task, padding}; +use std::sync::Arc; -pub struct Register { - state: State, - name: Input, - email: Input, - password: Input, - repeat: Input, +pub struct Register { + name: Input, + email: Input, + password: Input, + repeat: Input, show_password: bool, + + state: State, + service: Arc>, } enum State { None, @@ -18,25 +27,15 @@ enum State { Error(String), } -pub enum Request { +pub enum Event { SwitchToLogin, - SimpleValidation(Field), Task(Task), - Register { - name: String, - email: String, - password: String, - }, + Authenticated(Authenticated), } -pub enum Field { - Name(String), - Email(String), - Password(String), -} - -pub enum RequestResult { - Error(String), - Validation(Field, Validation), +impl From> for Event { + fn from(value: Task) -> Self { + Self::Task(value) + } } #[derive(Debug, Clone)] @@ -54,98 +53,110 @@ pub enum Message { RegisterPressed, LoginPressed, + + RequestResult(Arc>), } -impl Default for Register { - fn default() -> Self { - Self::new() - } -} - -impl Register { - pub const fn new() -> Self { +impl Register { + pub fn new(service: Arc>) -> Self { Self { - state: State::None, name: Input::new("register_name"), email: Input::new("register_email"), password: Input::new("register_password"), repeat: Input::new("register_repeat"), show_password: false, + + state: State::None, + service, } } - pub fn handle_result(&mut self, result: RequestResult) { - match result { - RequestResult::Error(e) => self.state = State::Error(e), - RequestResult::Validation(field, validation) => match &field { - Field::Name(s) => self.name.apply_if_eq(s, validation), - Field::Email(s) => self.email.apply_if_eq(s, validation), - Field::Password(s) => self.password.apply_if_eq(s, validation), - }, - } - } - - #[inline] fn check_passwords(&mut self) { - if self.password.value() != self.repeat.value() { - self.repeat.set_error("passwords are different".into()); + if self.password.as_ref() != self.repeat.as_ref() { + self.repeat.set_error(&"passwords are different"); } } - pub fn update(&mut self, message: Message) -> Option { - Some(match message { - Message::NameChanged(s) => { - self.name.update(s.clone()); - Request::SimpleValidation(Field::Name(s)) - } - Message::EmailChanged(s) => { - self.email.update(s.clone()); - Request::SimpleValidation(Field::Email(s)) - } + + pub fn update(&mut self, message: Message) -> Option { + match message { + Message::NameChanged(s) => self.name.update(s), + Message::EmailChanged(s) => self.email.update(s), Message::PasswordChanged(s) => { - self.password.update(s.clone()); + self.password.update(s); self.check_passwords(); - Request::SimpleValidation(Field::Password(s)) } Message::RepeatChanged(s) => { self.repeat.update(s); self.check_passwords(); - return None; } + Message::ShowPasswordToggled(b) => self.show_password = b, - Message::ShowPasswordToggled(b) => { - self.show_password = b; - return None; - } - - Message::NameSubmitted if !self.name.submittable() => return None, - Message::NameSubmitted => Request::Task(self.email.focus()), - Message::EmailSubmitted if !self.email.submittable() => return None, - Message::EmailSubmitted => Request::Task(self.password.focus()), - Message::PasswordSubmitted if !self.password.submittable() => return None, - Message::PasswordSubmitted => Request::Task(self.repeat.focus()), + Message::NameSubmitted if self.name.critical() => (), + Message::NameSubmitted => return Some(self.email.focus().into()), + Message::EmailSubmitted if self.email.critical() => (), + Message::EmailSubmitted => return Some(self.password.focus().into()), + Message::PasswordSubmitted if self.password.critical() => (), + Message::PasswordSubmitted => return Some(self.repeat.focus().into()), + Message::RepeatSubmitted if self.repeat.critical() => (), Message::RegisterPressed | Message::RepeatSubmitted => { - if !self.name.submittable() { - Request::Task(self.name.focus()) - } else if !self.email.submittable() { - Request::Task(self.email.focus()) - } else if !self.password.submittable() { - Request::Task(self.password.focus()) - } else if !self.repeat.submittable() { - Request::Task(self.repeat.focus()) - } else { - self.state = State::Requesting; - - Request::Register { - name: self.name.value().into(), - email: self.email.value().into(), - password: self.password.value().into(), - } + if self.repeat.critical() { + return Some(self.repeat.focus().into()); } + + let register_data = RegisterData { + name: match self.name.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + email: match self.email.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + password: match self.password.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + }; + + self.state = State::Requesting; + let arc = self.service.clone(); + + return Some( + Task::perform( + async move { + let Some(mut service) = arc.try_lock() else { + return Err(Error::Other( + "other authentication request is being performed".into(), + )); + }; + service.register(register_data).await + }, + |r| Message::RequestResult(Arc::new(r)), + ) + .into(), + ); } - Message::LoginPressed => Request::SwitchToLogin, - }) + Message::LoginPressed => return Some(Event::SwitchToLogin), + Message::RequestResult(r) => match &*r { + Ok(a) => return Some(Event::Authenticated(a.clone())), + + Err(e) => { + self.state = State::None; + match e { + Error::NameExists => self.name.set_warning(e), + Error::EmailExists => self.email.set_warning(e), + Error::IncorrectPassword => self.password.set_warning(e), + Error::InvalidPassword(_) => self.password.set_error(e), + + _ => self.state = State::Error(e.to_string()), + } + } + }, + } + + None } pub fn view(&self) -> iced::Element { @@ -194,7 +205,7 @@ impl Register { ) } - pub fn title(&self) -> std::borrow::Cow { + pub fn title(&self) -> String { let errors = [ self.name.error(), self.email.error(), diff --git a/3/coursework/src/view/src/widget.rs b/3/coursework/src/src/widget.rs similarity index 89% rename from 3/coursework/src/view/src/widget.rs rename to 3/coursework/src/src/widget.rs index e9e3f7c..d30c20f 100644 --- a/3/coursework/src/view/src/widget.rs +++ b/3/coursework/src/src/widget.rs @@ -14,10 +14,13 @@ pub fn centerbox<'a, Message: 'a>( /// Scrollable but in both vertical and horizontal directions pub fn scroll<'a, Message: 'a>(content: impl Into>) -> Element<'a, Message> { - Scrollable::with_direction(content, scrollable::Direction::Both { - vertical: scrollable::Scrollbar::default(), - horizontal: scrollable::Scrollbar::default(), - }) + Scrollable::with_direction( + content, + scrollable::Direction::Both { + vertical: scrollable::Scrollbar::default(), + horizontal: scrollable::Scrollbar::default(), + }, + ) .into() } diff --git a/3/coursework/src/view/Cargo.toml b/3/coursework/src/view/Cargo.toml deleted file mode 100644 index b558b15..0000000 --- a/3/coursework/src/view/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "view" -version = "0.1.0" -edition = "2024" - -[dependencies] -iced = { version = "0.13.1", features = ["lazy", "tokio"] } diff --git a/3/coursework/src/view/src/authentication.rs b/3/coursework/src/view/src/authentication.rs deleted file mode 100644 index f5685a3..0000000 --- a/3/coursework/src/view/src/authentication.rs +++ /dev/null @@ -1,104 +0,0 @@ -pub mod login; -pub mod register; - -use crate::input::Validation; - -use iced::Task; - -pub struct Authentication { - screen: Screen, - login: login::Login, - register: register::Register, -} -pub enum Screen { - Login, - Register, -} - -#[derive(Debug)] -pub enum Message { - Login(login::Message), - Register(register::Message), -} - -pub enum Request { - Task(Task), - SimpleLoginValidation(login::Field), - SimpleRegisterValidation(register::Field), - Login { - login: String, - password: String, - }, - Register { - name: String, - email: String, - password: String, - }, -} - -pub enum RequestResult { - Error(String), - LoginValidation(login::Field, Validation), - RegisterValidation(register::Field, Validation), -} - -impl Default for Authentication { - fn default() -> Self { - Self::new() - } -} - -impl Authentication { - pub const fn new() -> Self { - Self { - screen: Screen::Login, - login: Login::new(), - register: Register::new(), - } - } - - pub fn update(&mut self, message: Message) -> Option { - Some(match message { - Message::Login(message) => match self.login.update(message)? { - login::Request::SwitchToRegister => { - self.screen = Screen::Register; - return None; - } - login::Request::SimpleValidation(x) => Request::SimpleLoginValidation(x), - login::Request::Task(task) => Request::Task(task.map(Message::Login)), - login::Request::Login { login, password } => Request::Login { login, password }, - }, - Message::Register(message) => match self.register.update(message)? { - register::Request::SwitchToLogin => { - self.screen = Screen::Login; - return None; - } - register::Request::SimpleValidation(x) => Request::SimpleRegisterValidation(x), - register::Request::Task(task) => Request::Task(task.map(Message::Register)), - register::Request::Register { - name, - email, - password, - } => Request::Register { - name, - email, - password, - }, - }, - }) - } - - pub fn view(&self) -> iced::Element { - match self.screen { - Screen::Login => self.login.view().map(Message::Login), - Screen::Register => self.register.view().map(Message::Register), - } - } - - pub fn title(&self) -> std::borrow::Cow { - match self.screen { - Screen::Login => self.login.title(), - Screen::Register => self.register.title(), - } - } -} diff --git a/3/coursework/src/view/src/authentication/login.rs b/3/coursework/src/view/src/authentication/login.rs deleted file mode 100644 index d0f2e3b..0000000 --- a/3/coursework/src/view/src/authentication/login.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::input::{Input, Validation}; -use crate::widget::centerbox; - -use iced::widget::{Space, button, checkbox, column, container, row, text}; -use iced::{Length, Task, padding}; - -pub struct Login { - state: State, - login: Input, - password: Input, - show_password: bool, -} -enum State { - None, - Requesting, - Error(String), -} - -pub enum Request { - SwitchToRegister, - SimpleValidation(Field), - Task(Task), - Login { login: String, password: String }, -} -pub enum Field { - Login(String), - Password(String), -} - -pub enum RequestResult { - Error(String), - Validation(Field, Validation), -} - -#[derive(Debug, Clone)] -pub enum Message { - LoginChanged(String), - PasswordChanged(String), - ShowPasswordToggled(bool), - - LoginSubmitted, - PasswordSubmitted, - - LoginPressed, - RegisterPressed, -} - -impl Default for Login { - fn default() -> Self { - Self::new() - } -} - -impl Login { - pub const fn new() -> Self { - Self { - state: State::None, - login: Input::new("login_name"), - password: Input::new("login_password"), - show_password: false, - } - } - - pub fn handle_result(&mut self, result: RequestResult) { - match result { - RequestResult::Error(e) => self.state = State::Error(e), - RequestResult::Validation(field, validation) => match &field { - Field::Login(s) => self.login.apply_if_eq(s, validation), - Field::Password(s) => self.password.apply_if_eq(s, validation), - }, - } - } - - pub fn update(&mut self, message: Message) -> Option { - Some(match message { - Message::LoginChanged(s) => { - self.login.update(s.clone()); - Request::SimpleValidation(Field::Login(s)) - } - Message::PasswordChanged(s) => { - self.password.update(s.clone()); - Request::SimpleValidation(Field::Password(s)) - } - - Message::ShowPasswordToggled(b) => { - self.show_password = b; - return None; - } - - Message::LoginSubmitted if !self.login.submittable() => return None, - Message::LoginSubmitted => Request::Task(self.login.focus()), - - Message::LoginPressed | Message::PasswordSubmitted => { - if !self.login.submittable() { - Request::Task(self.login.focus()) - } else if !self.password.submittable() { - Request::Task(self.password.focus()) - } else { - self.state = State::Requesting; - - Request::Login { - login: self.login.value().into(), - password: self.password.value().into(), - } - } - } - - Message::RegisterPressed => Request::SwitchToRegister, - }) - } - - pub fn view(&self) -> iced::Element { - centerbox( - column![ - container(text(self.title()).size(20)) - .center_x(Length::Fill) - .padding(padding::bottom(10)), - self.login - .view("Email or Username") - .on_input(Message::LoginChanged) - .on_submit(Message::LoginSubmitted), - self.password - .view("Password") - .on_input(Message::PasswordChanged) - .on_submit(Message::PasswordSubmitted) - .secure(!self.show_password), - checkbox("Show password", self.show_password) - .on_toggle(Message::ShowPasswordToggled), - row![ - button(text("Register").center().size(18)) - .on_press(Message::RegisterPressed) - .style(button::secondary) - .width(Length::FillPortion(3)) - .padding(10), - Space::with_width(Length::FillPortion(2)), - button(text("Login").center().size(18)) - .on_press(Message::LoginPressed) - .style(button::primary) - .width(Length::FillPortion(3)) - .padding(10) - ] - .padding(padding::top(15)), - ] - .width(Length::Fixed(250.)) - .spacing(20), - ) - } - - pub fn title(&self) -> std::borrow::Cow { - let errors = [ - self.login.error(), - self.password.error(), - self.login.warning(), - self.password.warning(), - ]; - let error = errors.into_iter().flatten().next(); - - match &self.state { - State::None => error.map_or_else(|| "Login".into(), Into::into), - State::Requesting => "Requesting...".into(), - State::Error(e) => e.into(), - } - } -} diff --git a/3/coursework/src/view/src/input.rs b/3/coursework/src/view/src/input.rs deleted file mode 100644 index 5e8a70a..0000000 --- a/3/coursework/src/view/src/input.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::widget::text_input::{error, success, warning}; - -use iced::widget::{TextInput, text_input, text_input::default}; - -pub struct Input { - id: &'static str, - value: String, - warning: Option, - state: State, -} -enum State { - None, - Valid, - Invalid(String), -} - -pub enum Validation { - Valid, - Warning(String), - Invalid(String), -} - -impl Input { - pub const fn new(id: &'static str) -> Self { - Self { - id, - value: String::new(), - warning: None, - state: State::None, - } - } - - pub fn value(&self) -> &str { - self.value.as_ref() - } - pub fn error(&self) -> Option<&str> { - match &self.state { - State::Invalid(e) => Some(e.as_ref()), - _ => None, - } - } - pub fn warning(&self) -> Option<&str> { - self.warning.as_ref().map(AsRef::as_ref) - } - // pub fn submit(&self) -> Result { - // match &self.state { - // State::Invalid(e) => Err(e.as_ref()), - // State::None | State::Valid => Ok(self.value.clone()), - // } - // } - pub const fn submittable(&self) -> bool { - !matches!(self.state, State::Invalid(_)) - } - - pub fn update(&mut self, value: String) { - self.value = value; - self.warning = None; - self.state = State::None; - } - pub fn set_error(&mut self, value: String) { - self.state = State::Invalid(value); - } - pub fn set_warning(&mut self, value: String) { - self.warning = Some(value); - } - pub fn apply(&mut self, validation: Validation) { - match validation { - Validation::Valid => self.state = State::Valid, - Validation::Warning(w) => self.warning = Some(w), - Validation::Invalid(e) => self.state = State::Invalid(e), - } - } - pub fn apply_if_eq(&mut self, value: &str, validation: Validation) { - if self.value == value { - self.apply(validation); - } - } - - pub fn focus(&self) -> iced::Task { - iced::widget::text_input::focus(self.id) - } - pub fn view(&self, placeholder: &str) -> TextInput { - text_input(placeholder, &self.value) - .id(self.id) - .padding(12) - .style(match self.state { - State::None if self.warning.is_none() => default, - State::Valid if self.warning.is_none() => success, - State::Invalid(_) => error, - _ => warning, - }) - } -} diff --git a/3/coursework/src/view/src/lib.rs b/3/coursework/src/view/src/lib.rs deleted file mode 100644 index df79b91..0000000 --- a/3/coursework/src/view/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod widget; - -mod input; -pub use input::Validation; - -pub mod authentication; -pub use authentication::{Authentication, login, register}; From d273e98e02234365b805053d71f7b4e3e5eaa08a Mon Sep 17 00:00:00 2001 From: NUREH8 Date: Sat, 8 Feb 2025 16:46:35 +0200 Subject: [PATCH 09/13] Initial commit --- 3/coursework/src/LICENSE | 9 +++++++++ 3/coursework/src/README.md | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 3/coursework/src/LICENSE create mode 100644 3/coursework/src/README.md diff --git a/3/coursework/src/LICENSE b/3/coursework/src/LICENSE new file mode 100644 index 0000000..a669780 --- /dev/null +++ b/3/coursework/src/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2025 NUREH8 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/3/coursework/src/README.md b/3/coursework/src/README.md new file mode 100644 index 0000000..d62ecfc --- /dev/null +++ b/3/coursework/src/README.md @@ -0,0 +1,3 @@ +# repo + +Coursework that is not working. \ No newline at end of file From d01aa8d3c54ca4e52ca31f93524967b09c6e3fc6 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Wed, 12 Feb 2025 09:04:19 +0200 Subject: [PATCH 10/13] Detach authentication --- 3/coursework/src/README.md | 5 +- 3/coursework/src/assets/init/init.sql | 4 +- .../src/data/src/adapter/mysql/search.rs | 10 +-- 3/coursework/src/data/src/lib.rs | 3 +- 3/coursework/src/data/src/port/search.rs | 2 +- .../service/src/authentication/contract.rs | 2 +- 3/coursework/src/service/src/lib.rs | 4 +- 3/coursework/src/service/src/search.rs | 9 ++ .../src/service/src/search/adapter.rs | 43 ++++++++++ .../src/service/src/search/contract.rs | 60 ++++++++++++++ .../src/service/src/search/repository.rs | 6 ++ .../src/service/src/search/service.rs | 27 ++++++ 3/coursework/src/src/authentication.rs | 83 +++++++++++++++++++ .../src/src/{ => authentication}/login.rs | 7 +- .../src/src/{ => authentication}/register.rs | 22 +++-- 3/coursework/src/src/main.rs | 57 +++---------- 16 files changed, 278 insertions(+), 66 deletions(-) create mode 100644 3/coursework/src/service/src/search.rs create mode 100644 3/coursework/src/service/src/search/adapter.rs create mode 100644 3/coursework/src/service/src/search/contract.rs create mode 100644 3/coursework/src/service/src/search/repository.rs create mode 100644 3/coursework/src/service/src/search/service.rs create mode 100644 3/coursework/src/src/authentication.rs rename 3/coursework/src/src/{ => authentication}/login.rs (96%) rename 3/coursework/src/src/{ => authentication}/register.rs (91%) diff --git a/3/coursework/src/README.md b/3/coursework/src/README.md index d393cad..8376556 100644 --- a/3/coursework/src/README.md +++ b/3/coursework/src/README.md @@ -1,4 +1,4 @@ -# Stuff that helped: +# Stuff that helped * Architecture: - [How to apply hexagonal architecture to Rust](https://www.barrage.net/blog/technology/how-to-apply-hexagonal-architecture-to-rust) @@ -11,9 +11,10 @@ - [Building a simple text editor with iced, a cross-platform GUI library for Rust](https://www.youtube.com/watch?v=gcBJ7cPSALo) - [Unofficial Iced Guide](https://jl710.github.io/iced-guide/) - [icebreaker](https://github.com/hecrj/icebreaker) + - [Halloy](https://github.com/squidowl/halloy) --- > _The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise._ -— Edsger W. Dijkstra +— _Edsger W. Dijkstra_ diff --git a/3/coursework/src/assets/init/init.sql b/3/coursework/src/assets/init/init.sql index 6d1981a..73da14c 100644 --- a/3/coursework/src/assets/init/init.sql +++ b/3/coursework/src/assets/init/init.sql @@ -22,7 +22,7 @@ CREATE TABLE PackageBases ( description VARCHAR(510) NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- User roles for working on packages: flagger, packager, submitter, maintainer, etc. @@ -62,7 +62,7 @@ CREATE TABLE Packages ( flagged_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (base) REFERENCES PackageBases (id) ON DELETE CASCADE ); diff --git a/3/coursework/src/data/src/adapter/mysql/search.rs b/3/coursework/src/data/src/adapter/mysql/search.rs index 684cbf0..0dbc964 100644 --- a/3/coursework/src/data/src/adapter/mysql/search.rs +++ b/3/coursework/src/data/src/adapter/mysql/search.rs @@ -1,13 +1,13 @@ +use crate::Result; use crate::port::search::{Data, Entry, Mode, Order, SearchRepository}; -use crate::{Result, adapter::mysql::search}; // use chrono::Utc; use futures::TryStreamExt; use sqlx::{Executor, MySql, QueryBuilder, Row}; -pub struct UserAdapter; +pub struct SearchAdapter; -impl SearchRepository for UserAdapter +impl SearchRepository for SearchAdapter where E: Send, for<'a> &'a E: Executor<'a, Database = MySql>, @@ -139,13 +139,13 @@ mod tests { let data = Data { mode: Mode::NameAndDescription, order: Order::UpdatedAt, - search: Search::new("f")?, + search: Search::new("f").map_err(|e| e.1)?, limit: 50, exact: true, ascending: false, }; - UserAdapter::search(&pool, data).await?; + SearchAdapter::search(&pool, data).await?; Ok(()) } diff --git a/3/coursework/src/data/src/lib.rs b/3/coursework/src/data/src/lib.rs index ac3eebb..0713e21 100644 --- a/3/coursework/src/data/src/lib.rs +++ b/3/coursework/src/data/src/lib.rs @@ -13,10 +13,11 @@ pub use chrono::Utc; pub use adapter::mysql::base::BaseAdapter as MySqlBaseAdapter; pub use adapter::mysql::package::PackageAdapter as MySqlPackageAdapter; pub use adapter::mysql::user::UserAdapter as MySqlUserAdapter; +pub use adapter::mysql::search::SearchAdapter as MySqlSearchAdapter; pub use atomic::Atomic; pub use connect::*; pub use port::base::{Base, BaseRepository}; pub use port::package::{Package, PackageRepository}; +pub use port::search::{Search, SearchRepository}; pub use port::user::{User, UserRepository}; pub use port::*; - diff --git a/3/coursework/src/data/src/port/search.rs b/3/coursework/src/data/src/port/search.rs index 0475992..8a8f33a 100644 --- a/3/coursework/src/data/src/port/search.rs +++ b/3/coursework/src/data/src/port/search.rs @@ -23,7 +23,7 @@ pub struct Data { pub order: Order, pub search: Search, - pub limit: u8, + pub limit: u16, pub exact: bool, pub ascending: bool, } diff --git a/3/coursework/src/service/src/authentication/contract.rs b/3/coursework/src/service/src/authentication/contract.rs index 0d80243..42b3d06 100644 --- a/3/coursework/src/service/src/authentication/contract.rs +++ b/3/coursework/src/service/src/authentication/contract.rs @@ -102,7 +102,7 @@ impl TryFrom for Name { fn try_from(value: String) -> Result { #[derive(Validate)] #[garde(transparent)] - struct Username<'a>(#[garde(ascii, length(chars, min = 2, max = 31))] &'a str); + struct Username<'a>(#[garde(alphanumeric, length(chars, min = 2, max = 31))] &'a str); match Username(value.as_str()).validate() { Ok(()) => (), diff --git a/3/coursework/src/service/src/lib.rs b/3/coursework/src/service/src/lib.rs index 9111453..60c03cb 100644 --- a/3/coursework/src/service/src/lib.rs +++ b/3/coursework/src/service/src/lib.rs @@ -1,8 +1,8 @@ pub mod authentication; +pub mod search; pub use authentication::{ Authenticated, AuthenticationAdapter, AuthenticationContract, AuthenticationRepository, AuthenticationService, }; - -// pub +pub use search::{Search, SearchAdapter, SearchContract, SearchRepository, SearchService}; diff --git a/3/coursework/src/service/src/search.rs b/3/coursework/src/service/src/search.rs new file mode 100644 index 0000000..142e065 --- /dev/null +++ b/3/coursework/src/service/src/search.rs @@ -0,0 +1,9 @@ +pub mod adapter; +pub mod contract; +pub mod repository; +pub mod service; + +pub use adapter::*; +pub use contract::*; +pub use repository::*; +pub use service::*; diff --git a/3/coursework/src/service/src/search/adapter.rs b/3/coursework/src/service/src/search/adapter.rs new file mode 100644 index 0000000..faa766c --- /dev/null +++ b/3/coursework/src/service/src/search/adapter.rs @@ -0,0 +1,43 @@ +use data::search::*; +use data::{Connect, Result}; + +use std::marker::PhantomData; + +pub struct SearchAdapter +where + C: Send, + D: Connect + Sync, + UR: SearchRepository + Sync, +{ + driver: D, + _search_repository: PhantomData, +} + +impl SearchAdapter +where + C: Send, + D: Connect + Sync, + UR: SearchRepository + Sync, +{ + pub const fn new(driver: D) -> Self { + Self { + driver, + _search_repository: PhantomData, + } + } +} + +impl super::SearchRepository for SearchAdapter +where + C: Send, //+ Sync, + D: Connect + Sync, + SR: SearchRepository + Sync, +{ + async fn search(&self, data: Data) -> Result> { + let c = self.driver.open_connection().await?; + let result = SR::search(&c, data).await?; + D::close_connection(c).await?; + + Ok(result) + } +} diff --git a/3/coursework/src/service/src/search/contract.rs b/3/coursework/src/service/src/search/contract.rs new file mode 100644 index 0000000..305b5e3 --- /dev/null +++ b/3/coursework/src/service/src/search/contract.rs @@ -0,0 +1,60 @@ +use data::{BoxDynError, search}; +pub use data::{ + Result, Validation, + search::{Mode, Order, Entry}, +}; + +use derive_more::{Deref, Into}; +use garde::Validate; + +pub trait SearchContract: Send { + fn search(&self, data: Data) -> impl Future>> + Send; +} + +pub struct Data { + pub mode: Mode, + pub order: Order, + pub search: Search, + + pub limit: u16, + pub exact: bool, + pub ascending: bool, +} + +impl From for search::Data { + fn from(value: Data) -> Self { + Self { + mode: value.mode, + order: value.order, + search: value.search.into(), + limit: value.limit, + exact: value.exact, + ascending: value.ascending, + } + } +} + +pub type ReturnError = (T, BoxDynError); + +#[derive(Clone, Deref, Into)] +pub struct Search(search::Search); +impl AsRef for Search { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl TryFrom for Search { + type Error = ReturnError; + + fn try_from(value: String) -> Result { + #[derive(Validate)] + #[garde(transparent)] + struct Check<'a>(#[garde(ascii, length(chars, min = 1, max = 255))] &'a str); + + match Check(value.as_str()).validate() { + Ok(()) => (), + Err(e) => return Err((value, e.into())), + } + Ok(Self(search::Search::new(value)?)) + } +} diff --git a/3/coursework/src/service/src/search/repository.rs b/3/coursework/src/service/src/search/repository.rs new file mode 100644 index 0000000..0626718 --- /dev/null +++ b/3/coursework/src/service/src/search/repository.rs @@ -0,0 +1,6 @@ +use data::Result; +use data::search::{Data, Entry}; + +pub trait SearchRepository { + fn search(&self, data: Data) -> impl Future>> + Send; +} diff --git a/3/coursework/src/service/src/search/service.rs b/3/coursework/src/service/src/search/service.rs new file mode 100644 index 0000000..48a9e90 --- /dev/null +++ b/3/coursework/src/service/src/search/service.rs @@ -0,0 +1,27 @@ +use super::{Data, Result, SearchContract, SearchRepository}; +use data::search; + +pub struct SearchService +where + R: SearchRepository, +{ + pub(crate) repository: R, +} + +impl SearchService +where + R: SearchRepository, +{ + pub const fn new(repository: R) -> Self { + Self { repository } + } +} + +impl SearchContract for SearchService +where + R: SearchRepository + Send + Sync, +{ + async fn search(&self, data: Data) -> Result> { + self.repository.search(data.into()).await + } +} diff --git a/3/coursework/src/src/authentication.rs b/3/coursework/src/src/authentication.rs new file mode 100644 index 0000000..433bc8e --- /dev/null +++ b/3/coursework/src/src/authentication.rs @@ -0,0 +1,83 @@ +mod login; +mod register; + +use login::Login; +use register::Register; + +use service::{Authenticated, AuthenticationContract}; + +use iced::{Element, Task, futures::lock::Mutex}; +use std::sync::Arc; + +pub struct Authentication { + login: Login, + register: Register, + screen: Screen, +} + +enum Screen { + Login, + Register, +} + +#[derive(Debug)] +pub enum Message { + Login(login::Message), + Register(register::Message), +} + +pub enum Event { + Task(Task), + Authenticated(Authenticated), +} +impl From> for Event { + fn from(value: Task) -> Self { + Self::Task(value) + } +} + +impl Authentication { + pub fn new(service: Arc>) -> Self { + Self { + login: Login::new(service.clone()), + register: Register::new(service), + screen: Screen::Login, + } + } + + pub fn update(&mut self, message: Message) -> Option { + Some(match message { + Message::Login(message) => match self.login.update(message)? { + login::Event::SwitchToRegister => { + self.screen = Screen::Register; + return None; + } + + login::Event::Task(task) => task.map(Message::Login).into(), + login::Event::Authenticated(x) => Event::Authenticated(x), + }, + Message::Register(message) => match self.register.update(message)? { + register::Event::SwitchToLogin => { + self.screen = Screen::Login; + return None; + } + register::Event::Task(task) => task.map(Message::Register).into(), + register::Event::Authenticated(x) => Event::Authenticated(x), + }, + }) + } + + pub fn view(&self) -> Element { + match self.screen { + Screen::Login => self.login.view().map(Message::Login), + Screen::Register => self.register.view().map(Message::Register), + } + } + + pub fn title(&self) -> String { + match self.screen { + Screen::Login => self.login.title(), + Screen::Register => self.register.title(), + } + } +} diff --git a/3/coursework/src/src/login.rs b/3/coursework/src/src/authentication/login.rs similarity index 96% rename from 3/coursework/src/src/login.rs rename to 3/coursework/src/src/authentication/login.rs index 880db49..e5990fc 100644 --- a/3/coursework/src/src/login.rs +++ b/3/coursework/src/src/authentication/login.rs @@ -22,6 +22,7 @@ pub struct Login { enum State { None, Requesting, + Success, Error(String), } @@ -104,7 +105,10 @@ impl Login { ); } Message::RequestResult(r) => match &*r { - Ok(a) => return Some(Event::Authenticated(a.clone())), + Ok(a) => { + self.state = State::Success; + return Some(Event::Authenticated(a.clone())); + } Err(e) => { self.state = State::None; @@ -170,6 +174,7 @@ impl Login { match &self.state { State::None => error.map_or_else(|| "Login".into(), Into::into), + State::Success => "Success".into(), State::Requesting => "Requesting...".into(), State::Error(e) => e.into(), } diff --git a/3/coursework/src/src/register.rs b/3/coursework/src/src/authentication/register.rs similarity index 91% rename from 3/coursework/src/src/register.rs rename to 3/coursework/src/src/authentication/register.rs index e62876c..b1a78e4 100644 --- a/3/coursework/src/src/register.rs +++ b/3/coursework/src/src/authentication/register.rs @@ -1,4 +1,4 @@ -use crate::input::Input; +use crate::input::{self, Input, Value}; use crate::widget::centerbox; use service::authentication::{self, Email, Name, Password, RegisterData}; use service::{ @@ -15,7 +15,7 @@ pub struct Register { name: Input, email: Input, password: Input, - repeat: Input, + repeat: Input, show_password: bool, state: State, @@ -23,6 +23,7 @@ pub struct Register { } enum State { None, + Success, Requesting, Error(String), } @@ -72,7 +73,10 @@ impl Register { } fn check_passwords(&mut self) { - if self.password.as_ref() != self.repeat.as_ref() { + if self.password.as_ref() == self.repeat.as_ref() { + self.repeat + .set_value(Value::Valid(self.repeat.as_ref().to_string())); + } else { self.repeat.set_error(&"passwords are different"); } } @@ -86,7 +90,7 @@ impl Register { self.check_passwords(); } Message::RepeatChanged(s) => { - self.repeat.update(s); + self.repeat.set_value(Value::Valid(s)); self.check_passwords(); } Message::ShowPasswordToggled(b) => self.show_password = b, @@ -97,10 +101,10 @@ impl Register { Message::EmailSubmitted => return Some(self.password.focus().into()), Message::PasswordSubmitted if self.password.critical() => (), Message::PasswordSubmitted => return Some(self.repeat.focus().into()), - Message::RepeatSubmitted if self.repeat.critical() => (), + Message::RepeatSubmitted if self.repeat.error().is_some() => (), Message::RegisterPressed | Message::RepeatSubmitted => { - if self.repeat.critical() { + if self.repeat.error().is_some() { return Some(self.repeat.focus().into()); } @@ -140,7 +144,10 @@ impl Register { Message::LoginPressed => return Some(Event::SwitchToLogin), Message::RequestResult(r) => match &*r { - Ok(a) => return Some(Event::Authenticated(a.clone())), + Ok(a) => { + self.state = State::Success; + return Some(Event::Authenticated(a.clone())) + } Err(e) => { self.state = State::None; @@ -220,6 +227,7 @@ impl Register { match &self.state { State::None => error.map_or_else(|| "Register".into(), Into::into), + State::Success => "Success".into(), State::Requesting => "Requesting...".into(), State::Error(e) => e.into(), } diff --git a/3/coursework/src/src/main.rs b/3/coursework/src/src/main.rs index 7355706..407fc40 100644 --- a/3/coursework/src/src/main.rs +++ b/3/coursework/src/src/main.rs @@ -1,15 +1,12 @@ // mod main_window; // mod authentication; +mod authentication; mod input; -mod login; -mod register; mod widget; use std::sync::Arc; -use crate::login::Login; -use crate::register::Register; -// use crate::authentication::Authentication; +use crate::authentication::Authentication; // use crate::main_window::MainWindow; use data::{MySqlPool, MySqlUserAdapter, SqlxPool}; @@ -40,18 +37,9 @@ fn main() -> iced::Result { struct Repository { scale_factor: f64, main_id: window::Id, - login: - Login>>, - register: Register< + authentication: Authentication< AuthenticationService>, >, - screen: Screen, - // authentication: Authentication, -} - -enum Screen { - Login, - Register, } #[derive(Debug)] @@ -61,8 +49,7 @@ enum Message { WindowOpened(window::Id), WindowClosed(window::Id), - Login(login::Message), - Register(register::Message), + Authentecation(authentication::Message), // MainWindow(main_window::Message), } @@ -87,9 +74,7 @@ impl Repository { Self { scale_factor: 1.4, main_id, - login: Login::new(auth_service.clone()), - register: Register::new(auth_service), - screen: Screen::Login, + authentication: Authentication::new(auth_service), }, Task::batch([ open_task.map(Message::WindowOpened), @@ -112,24 +97,14 @@ impl Repository { return iced::exit(); } } - Message::Login(message) => { - if let Some(action) = self.login.update(message) { + Message::Authentecation(message) => { + if let Some(action) = self.authentication.update(message) { match action { - login::Event::SwitchToRegister => self.screen = Screen::Register, - login::Event::Task(task) => return task.map(Message::Login), - login::Event::Authenticated(authenticated) => { - log!("authenticated via login {:#?}", authenticated); + authentication::Event::Task(task) => { + return task.map(Message::Authentecation); } - } - } - } - Message::Register(message) => { - if let Some(action) = self.register.update(message) { - match action { - register::Event::SwitchToLogin => self.screen = Screen::Login, - register::Event::Task(task) => return task.map(Message::Register), - register::Event::Authenticated(authenticated) => { - log!("authenticated via register: {:#?}", authenticated); + authentication::Event::Authenticated(authenticated) => { + log!("authenticated via login {:#?}", authenticated); } } } @@ -145,10 +120,7 @@ impl Repository { fn view(&self, id: window::Id) -> Element { if self.main_id == id { // self.main_window.view().map(Message::MainWindow) - match self.screen { - Screen::Login => self.login.view().map(Message::Login), - Screen::Register => self.register.view().map(Message::Register), - } + self.authentication.view().map(Message::Authentecation) } else { center(row!["This window is unknown.", "It may be closed."]).into() } @@ -156,10 +128,7 @@ impl Repository { fn title(&self, _: window::Id) -> String { // "Repository".into() - match self.screen { - Screen::Login => self.login.title(), - Screen::Register => self.register.title(), - } + self.authentication.title() } fn subscription(&self) -> Subscription { From 61d91247e72c3c2939595a42fc49404a0f9d9683 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Wed, 12 Feb 2025 14:33:42 +0200 Subject: [PATCH 11/13] Search functionality --- 3/coursework/src/Cargo.lock | 39 +- 3/coursework/src/Cargo.toml | 1 + .../src/assets/init/{init.sql => 0-init.sql} | 0 3/coursework/src/assets/init/1-data.sql | 103 ++++++ .../src/data/src/adapter/mysql/search.rs | 4 +- 3/coursework/src/data/src/port/search.rs | 4 +- 3/coursework/src/src/authentication/login.rs | 3 +- .../src/src/authentication/register.rs | 13 +- 3/coursework/src/src/main.rs | 109 ++++-- 3/coursework/src/src/search.rs | 333 ++++++++++++++++++ 3/coursework/src/src/widget.rs | 2 +- 11 files changed, 556 insertions(+), 55 deletions(-) rename 3/coursework/src/assets/init/{init.sql => 0-init.sql} (100%) create mode 100644 3/coursework/src/assets/init/1-data.sql create mode 100644 3/coursework/src/src/search.rs diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index 696bd6a..e1c9c5b 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.12" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", @@ -2311,9 +2311,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", "simd-adler32", @@ -3257,6 +3257,7 @@ dependencies = [ "data", "iced", "service", + "strum", ] [[package]] @@ -3892,6 +3893,28 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strum" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.98", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4118,9 +4141,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.23" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", @@ -4999,9 +5022,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml index 3a63616..e12dad9 100644 --- a/3/coursework/src/Cargo.toml +++ b/3/coursework/src/Cargo.toml @@ -8,6 +8,7 @@ data = { path = "data" } service = { path = "service" } iced = { version = "0.13.1", features = ["tokio", "lazy"] } +strum = { version = "0.27.0", features = ["derive"] } [workspace] resolver = "2" diff --git a/3/coursework/src/assets/init/init.sql b/3/coursework/src/assets/init/0-init.sql similarity index 100% rename from 3/coursework/src/assets/init/init.sql rename to 3/coursework/src/assets/init/0-init.sql diff --git a/3/coursework/src/assets/init/1-data.sql b/3/coursework/src/assets/init/1-data.sql new file mode 100644 index 0000000..e7ea4b4 --- /dev/null +++ b/3/coursework/src/assets/init/1-data.sql @@ -0,0 +1,103 @@ +-- Insert Users +INSERT INTO Users (name, email, password, last_used) VALUES +('alice', 'alice@example.com', 'password123', NOW()), +('bob', 'bob@example.com', 'securepass', NOW()), +('charlie', 'charlie@example.com', 'charliepwd', NOW()), +('dave', 'dave@example.com', 'davepass', NOW()), +('eve', 'eve@example.com', 'evepwd', NOW()), +('frank', 'frank@example.com', 'frankpass', NOW()), +('grace', 'grace@example.com', 'gracepwd', NOW()), +('heidi', 'heidi@example.com', 'heidipwd', NOW()), +('ivan', 'ivan@example.com', 'ivanpass', NOW()), +('judy', 'judy@example.com', 'judypass', NOW()), +('mallory', 'mallory@example.com', 'mallorypwd', NOW()), +('oscar', 'oscar@example.com', 'oscarpass', NOW()), +('peggy', 'peggy@example.com', 'peggypwd', NOW()), +('trent', 'trent@example.com', 'trentpass', NOW()), +('victor', 'victor@example.com', 'victorpwd', NOW()); + +-- Insert PackageBases +INSERT INTO PackageBases (name, description) VALUES +('libcore', 'Core system libraries'), +('webframework', 'A modern web framework'), +('dataproc', 'Data processing toolkit'), +('authmodule', 'Authentication and authorization module'), +('networkstack', 'Networking utilities and stack'), +('uikit', 'UI Kit for building interfaces'), +('cryptoengine', 'Cryptographic library'), +('dbconnector', 'Database connectivity drivers'), +('imageproc', 'Image processing library'), +('audiokit', 'Audio toolkit'), +('videokit', 'Video processing toolkit'), +('mlcore', 'Machine Learning core library'), +('analyticspro', 'Advanced analytics toolkit'), +('monitoragent', 'System monitoring agent'), +('filesystem', 'Filesystem utilities'); + +-- Assign Roles to Users for PackageBases +INSERT INTO PackageBaseUserRoles (base, user, role, comment) VALUES +(1, 1, 1, 'Original submitter'), +(1, 2, 2, 'Packager for latest release'), +(2, 3, 3, 'Maintains stability'), +(2, 4, 4, 'Flags issues'), +(3, 5, 1, 'Initial submission'), +(3, 6, 3, 'Lead maintainer'), +(4, 7, 2, 'Core packager'), +(5, 8, 1, 'Submitted new version'), +(6, 9, 4, 'Flagged for performance issues'), +(7, 10, 3, 'Maintainer for security fixes'), +(8, 11, 2, 'Driver package manager'), +(9, 12, 1, 'Original contributor'), +(10, 13, 3, 'Maintains core features'), +(11, 14, 4, 'Reported critical bug'), +(12, 15, 2, 'Optimized build process'); + +-- Insert Packages +INSERT INTO Packages (base, name, version, description, url) VALUES +(1, 'libcore-utils', '1.0.0', 'Utilities for libcore', 'http://example.com/libcore-utils'), +(1, 'libcore-extended', '1.1.0', 'Extended functionalities', 'http://example.com/libcore-extended'), +(2, 'webframework-api', '2.0.0', 'REST API module', 'http://example.com/webframework-api'), +(2, 'webframework-cli', '2.1.0', 'Command-line tools', 'http://example.com/webframework-cli'), +(3, 'dataproc-engine', '3.0.1', 'Data processing engine', 'http://example.com/dataproc-engine'), +(4, 'authmodule-oauth', '4.2.0', 'OAuth module', 'http://example.com/authmodule-oauth'), +(5, 'networkstack-core', '5.5.0', 'Core network stack', 'http://example.com/networkstack-core'), +(6, 'uikit-designer', '6.0.3', 'UI designer toolkit', 'http://example.com/uikit-designer'), +(7, 'cryptoengine-hash', '7.1.1', 'Hash algorithms', 'http://example.com/cryptoengine-hash'), +(8, 'dbconnector-mysql', '8.0.0', 'MySQL connector', 'http://example.com/dbconnector-mysql'), +(9, 'imageproc-filters', '9.3.0', 'Image filters library', 'http://example.com/imageproc-filters'), +(10, 'audiokit-mixer', '10.2.1', 'Audio mixing toolkit', 'http://example.com/audiokit-mixer'), +(11, 'videokit-stream', '11.4.0', 'Video streaming tools', 'http://example.com/videokit-stream'), +(12, 'mlcore-algo', '12.0.2', 'ML algorithms', 'http://example.com/mlcore-algo'), +(13, 'analyticspro-dashboard', '13.5.1', 'Analytics dashboard', 'http://example.com/analyticspro-dashboard'); + +-- Insert PackageDependencies +INSERT INTO PackageDependencies (arch, requirement, description, package, dependency_type, dependency_package_name) VALUES +('x86_64', '>=1.0.0', 'Core dependency', 3, 1, 'libcore-utils'), +('x86_64', '>=2.0.0', 'Required for API', 4, 2, 'webframework-api'), +('arm64', '>=3.0.1', 'Optional analytics', 5, 4, 'analyticspro-dashboard'), +('x86_64', '>=5.5.0', 'Network stack dependency', 6, 1, 'networkstack-core'), +('x86_64', '>=4.2.0', 'Authentication module', 7, 1, 'authmodule-oauth'), +('x86_64', NULL, 'Database driver', 8, 1, 'dbconnector-mysql'), +('arm64', NULL, 'Machine learning algorithms', 9, 3, 'mlcore-algo'), +('x86_64', '>=6.0.3', 'UI designer toolkit', 10, 1, 'uikit-designer'), +('x86_64', NULL, 'Audio toolkit dependency', 11, 2, 'audiokit-mixer'), +('x86_64', '>=7.1.1', 'Hash functions', 12, 1, 'cryptoengine-hash'), +('arm64', NULL, 'Video streaming tools', 13, 4, 'videokit-stream'), +('x86_64', '>=9.3.0', 'Image filters', 14, 1, 'imageproc-filters'), +('x86_64', NULL, 'System monitoring agent', 15, 2, 'monitoragent'); + +-- Insert PackageRelations +INSERT INTO PackageRelations (arch, requirement, package, relation_type, relation_package_name) VALUES +('x86_64', '>=1.0.0', 3, 1, 'legacy-web-api'), -- conflicts +('x86_64', NULL, 4, 2, 'web-cli-tools'), -- provides +('arm64', NULL, 5, 3, 'old-dataproc'), -- replaces +('x86_64', '>=5.0.0', 6, 1, 'net-tools-legacy'), +('x86_64', NULL, 7, 2, 'crypto-lib'), +('x86_64', '>=4.0.0', 8, 3, 'db-driver-old'), +('arm64', NULL, 9, 1, 'imgproc-v1'), +('x86_64', NULL, 10, 2, 'audio-tools'), +('x86_64', '>=7.0.0', 11, 3, 'video-kit-old'), +('x86_64', NULL, 12, 1, 'ml-core-legacy'), +('x86_64', '>=6.0.0', 13, 2, 'analytics-pro-tools'), +('x86_64', NULL, 14, 3, 'monitor-agent-v1'), +('x86_64', '>=9.0.0', 15, 1, 'filesystem-old'); diff --git a/3/coursework/src/data/src/adapter/mysql/search.rs b/3/coursework/src/data/src/adapter/mysql/search.rs index 0dbc964..03ce524 100644 --- a/3/coursework/src/data/src/adapter/mysql/search.rs +++ b/3/coursework/src/data/src/adapter/mysql/search.rs @@ -111,8 +111,8 @@ where base_name: row.try_get("base_name")?, url: row.try_get("url")?, description: row.try_get("description")?, - submitter_id: row.try_get("submitter_id")?, - submitter_name: row.try_get("submitter_name")?, + // submitter_id: row.try_get("submitter_id")?, + // submitter_name: row.try_get("submitter_name")?, updated_at: row.try_get("updated_at")?, created_at: row.try_get("created_at")?, }); diff --git a/3/coursework/src/data/src/port/search.rs b/3/coursework/src/data/src/port/search.rs index 8a8f33a..2af96f3 100644 --- a/3/coursework/src/data/src/port/search.rs +++ b/3/coursework/src/data/src/port/search.rs @@ -37,8 +37,8 @@ pub struct Entry { pub base_name: Box, pub url: Option>, pub description: Box, - pub submitter_id: u64, - pub submitter_name: Box, + // pub submitter_id: u64, + // pub submitter_name: Box, pub updated_at: DateTime, pub created_at: DateTime, } diff --git a/3/coursework/src/src/authentication/login.rs b/3/coursework/src/src/authentication/login.rs index e5990fc..df0f216 100644 --- a/3/coursework/src/src/authentication/login.rs +++ b/3/coursework/src/src/authentication/login.rs @@ -1,9 +1,8 @@ use crate::input::Input; use crate::widget::centerbox; -use service::authentication; use service::{ Authenticated, AuthenticationContract, - authentication::{Error, LoginData, Result}, + authentication::{self, Error, LoginData, Result}, }; use iced::futures::lock::Mutex; diff --git a/3/coursework/src/src/authentication/register.rs b/3/coursework/src/src/authentication/register.rs index b1a78e4..6615775 100644 --- a/3/coursework/src/src/authentication/register.rs +++ b/3/coursework/src/src/authentication/register.rs @@ -101,13 +101,12 @@ impl Register { Message::EmailSubmitted => return Some(self.password.focus().into()), Message::PasswordSubmitted if self.password.critical() => (), Message::PasswordSubmitted => return Some(self.repeat.focus().into()), - Message::RepeatSubmitted if self.repeat.error().is_some() => (), - + Message::RegisterPressed | Message::RepeatSubmitted + if self.repeat.error().is_some() => + { + return Some(self.repeat.focus().into()); + } Message::RegisterPressed | Message::RepeatSubmitted => { - if self.repeat.error().is_some() { - return Some(self.repeat.focus().into()); - } - let register_data = RegisterData { name: match self.name.submit() { Ok(x) => x, @@ -146,7 +145,7 @@ impl Register { Message::RequestResult(r) => match &*r { Ok(a) => { self.state = State::Success; - return Some(Event::Authenticated(a.clone())) + return Some(Event::Authenticated(a.clone())); } Err(e) => { diff --git a/3/coursework/src/src/main.rs b/3/coursework/src/src/main.rs index 407fc40..0fe0b6d 100644 --- a/3/coursework/src/src/main.rs +++ b/3/coursework/src/src/main.rs @@ -1,47 +1,45 @@ -// mod main_window; -// mod authentication; mod authentication; mod input; +mod search; +//mod statistics; mod widget; use std::sync::Arc; use crate::authentication::Authentication; -// use crate::main_window::MainWindow; +use crate::search::Search; +//use crate::statistics::Statistics; -use data::{MySqlPool, MySqlUserAdapter, SqlxPool}; +use data::{MySqlPool, MySqlSearchAdapter, MySqlUserAdapter, SqlxPool}; use iced::{ Element, Subscription, Task, Theme, futures::lock::Mutex, widget::{center, row}, window, }; -use service::{AuthenticationAdapter, AuthenticationService}; - -// #[macro_export] -macro_rules! log { - ($($arg:tt)*) => { - #[cfg(debug_assertions)] - println!($($arg)*) - }; -} - -fn main() -> iced::Result { - iced::daemon(Repository::title, Repository::update, Repository::view) - .subscription(Repository::subscription) - .scale_factor(Repository::scale_factor) - .theme(Repository::theme) - .run_with(Repository::new) -} +use service::{ + Authenticated, AuthenticationAdapter, AuthenticationService, SearchAdapter, SearchService, +}; struct Repository { scale_factor: f64, main_id: window::Id, + screen: Screen, + + authenticated: Option, + + search: Search>>, authentication: Authentication< AuthenticationService>, >, } +enum Screen { + Search, + // Statistics, + Authentication, +} + #[derive(Debug)] enum Message { ScaleUp, @@ -49,8 +47,8 @@ enum Message { WindowOpened(window::Id), WindowClosed(window::Id), + Search(search::Message), Authentecation(authentication::Message), - // MainWindow(main_window::Message), } impl Repository { @@ -70,10 +68,20 @@ impl Repository { AuthenticationAdapter::new(pool.clone()), ))); + let search_service = Arc::new(Mutex::new(SearchService::new(SearchAdapter::new( + pool.clone(), + )))); + ( Self { - scale_factor: 1.4, main_id, + scale_factor: 1.4, + screen: Screen::Search, + + + authenticated: None, + + search: Search::new(search_service), authentication: Authentication::new(auth_service), }, Task::batch([ @@ -98,29 +106,48 @@ impl Repository { } } Message::Authentecation(message) => { - if let Some(action) = self.authentication.update(message) { - match action { + if let Some(event) = self.authentication.update(message) { + match event { authentication::Event::Task(task) => { return task.map(Message::Authentecation); } authentication::Event::Authenticated(authenticated) => { - log!("authenticated via login {:#?}", authenticated); + log!("authenticated as {:#?}", authenticated); + self.authenticated = Some(authenticated); + self.screen = Screen::Search; } } } - } // - // Message::MainWindow(message) => match self.main_window.update(message) { - // main_window::Action::None => (), - // main_window::Action::Task(task) => return task.map(Message::MainWindow), - // }, + } + Message::Search(m) => { + if let Some(event) = self.search.update(m) { + match event { + search::Event::Task(task) => { + return task.map(Message::Search); + } + search::Event::OpenPackage(id) => { + log!("opening package {id}") + } + search::Event::OpenBase(id) => { + log!("opening package base {id}") + } + search::Event::OpenURL(url) => { + log!("opening url {url}") + } + } + } + } } + Task::none() } fn view(&self, id: window::Id) -> Element { if self.main_id == id { - // self.main_window.view().map(Message::MainWindow) - self.authentication.view().map(Message::Authentecation) + match self.screen { + Screen::Search => self.search.view().map(Message::Search), + Screen::Authentication => self.authentication.view().map(Message::Authentecation), + } } else { center(row!["This window is unknown.", "It may be closed."]).into() } @@ -151,3 +178,19 @@ impl Repository { Theme::TokyoNight } } + +#[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + #[cfg(debug_assertions)] + println!($($arg)*) + }; +} + +fn main() -> iced::Result { + iced::daemon(Repository::title, Repository::update, Repository::view) + .subscription(Repository::subscription) + .scale_factor(Repository::scale_factor) + .theme(Repository::theme) + .run_with(Repository::new) +} diff --git a/3/coursework/src/src/search.rs b/3/coursework/src/src/search.rs new file mode 100644 index 0000000..bb84c15 --- /dev/null +++ b/3/coursework/src/src/search.rs @@ -0,0 +1,333 @@ +use crate::input::Input; +use crate::widget::{scroll, tip, url}; + +use iced::Length::Shrink; +use service::search::Data; +use service::{ + SearchContract, + search::{self, Entry, Result}, +}; + +use iced::widget::{Column, button, checkbox, column, container, lazy, pick_list, row, text}; +use iced::{Element, Length::Fill, Task, futures::lock::Mutex}; +use std::sync::Arc; +use strum::{Display, VariantArray}; + +pub struct Search { + input: Input, + mode: Mode, + order: Order, + ascending: bool, + exact: bool, + limit: u8, + + state: State, + service: Arc>, +} + +#[derive(Default)] +enum State { + #[default] + None, + Searching, + // Aborted, + Table(Table), + Error(String), +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, VariantArray)] +pub enum Mode { + Url, + Name, + #[strum(to_string = "Package Base")] + PackageBase, + Description, + #[strum(to_string = "Base description")] + BaseDescription, + #[default] + #[strum(to_string = "Name and Description")] + NameAndDescription, + User, + Flagger, + Packager, + Submitter, + Maintainer, +} +impl From for search::Mode { + fn from(value: Mode) -> Self { + match value { + Mode::Url => Self::Url, + Mode::Name => Self::Name, + Mode::PackageBase => Self::PackageBase, + Mode::Description => Self::Description, + Mode::BaseDescription => Self::BaseDescription, + Mode::NameAndDescription => Self::NameAndDescription, + Mode::User => Self::User, + Mode::Flagger => Self::Flagger, + Mode::Packager => Self::Packager, + Mode::Submitter => Self::Submitter, + Mode::Maintainer => Self::Maintainer, + } + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, VariantArray)] +pub enum Order { + Name, + Version, + #[strum(to_string = "Base Name")] + BaseName, + // Submitter, + #[default] + #[strum(to_string = "Last update")] + UpdatedAt, + #[strum(to_string = "Created time")] + CreatedAt, +} +impl From for search::Order { + fn from(value: Order) -> Self { + match value { + Order::Name => Self::Name, + Order::Version => Self::Version, + Order::BaseName => Self::BaseName, + Order::UpdatedAt => Self::UpdatedAt, + Order::CreatedAt => Self::CreatedAt, + } + } +} + +#[derive(Debug, Clone)] +pub enum Message { + // Search bar + Reset, + Search, + SearchChanged(String), + ModePicked(Mode), + OrderPicked(Order), + AscendingToggled(bool), + ExactToggled(bool), + ShowEntriesPicked(u8), + // Table + PackagePressed(u64), + BasePressed(u64), + URLPressed(Box), + + RequestResult(Arc>>), +} + +pub enum Event { + Task(Task), + OpenPackage(u64), + OpenBase(u64), + OpenURL(Box), +} +impl From> for Event { + fn from(value: Task) -> Self { + Self::Task(value) + } +} + +#[derive(Debug, Hash)] +struct Table(Vec); + +impl Table { + pub fn view(&self) -> Element<'static, Message> { + let mut table: Vec<_> = [ + "Package", // 0 + "Version", // 1 + "Base", // 2 + "URL", // 3 + "Description", // 4 + "Last Updated", // 5 + "Created", // 6 + ] + .into_iter() + .map(|s| { + let mut v = Vec::with_capacity(self.0.len()); + v.push(s.into()); + v.push("".into()); + v + }) + .collect(); + + for entry in &self.0 { + table[0].push(url(&entry.name, Message::PackagePressed(entry.id))); + table[1].push(text(entry.version.to_string()).into()); + table[2].push(url(&entry.base_name, Message::BasePressed(entry.base_id))); + table[3].push( + entry + .url + .as_ref() + .map_or("-".into(), |s| url(&"link", Message::URLPressed(s.clone()))), + ); + table[4].push(text(entry.description.to_string()).into()); + table[5].push(text(entry.updated_at.to_string()).into()); + table[6].push(text(entry.created_at.to_string()).into()); + // table[5].push(Element::from(column( entry .maintainers .iter() .map(|(id, s)| url(s, Message::UserPressed(*id))),))); + } + + scroll( + row(table + .into_iter() + .map(|v| Column::from_vec(v).spacing(5).into())) + .spacing(20) + .padding(30), + ) + } +} + +impl Search { + pub fn new(service: Arc>) -> Self { + Self { + input: Input::new("search_input"), + mode: Mode::NameAndDescription, + order: Order::UpdatedAt, + ascending: false, + exact: false, + limit: 25, + state: State::default(), + service, + } + } + + pub fn view(&self) -> Element { + let search_bar = container(scroll( + column![ + row![ + self.input + .view("Search") + .on_input(Message::SearchChanged) + .on_submit(Message::Search), + tip( + button("Go").on_press(Message::Search), + "Perform the search", + tip::Position::Bottom, + ), + ] + .spacing(10), + row![ + tip( + button("Reset").on_press(Message::Reset), + "Reset the search bar", + tip::Position::Bottom, + ), + tip( + pick_list(Mode::VARIANTS, Some(&self.mode), Message::ModePicked), + "Search mode", + tip::Position::Bottom, + ), + tip( + pick_list(Order::VARIANTS, Some(&self.order), Message::OrderPicked), + "Field used to sort the results", + tip::Position::Bottom, + ), + tip( + checkbox("Exact", self.exact).on_toggle(Message::ExactToggled), + "Exact search", + tip::Position::Bottom, + ), + tip( + checkbox("Ascending", self.ascending).on_toggle(Message::AscendingToggled), + "Sort order of results", + tip::Position::Bottom, + ), + tip( + pick_list( + [25, 50, 75, 100], + Some(self.limit), + Message::ShowEntriesPicked + ), + "Number of results to show", + tip::Position::Bottom, + ), + ] + .spacing(10) + ] + .padding(20) + .width(750) + .spacing(10), + )) + .center_x(Fill); + + column![ + search_bar, + match &self.state { + State::None => Element::from(""), + State::Searching => "Searching...".into(), + // State::Aborted => "Aborted".into(), + State::Error(e) => text(e).into(), + State::Table(table) => container(lazy(table, |t| t.view())).center_x(Fill).into(), + } + ] + .into() + } + + pub fn update(&mut self, message: Message) -> Option { + match message { + Message::SearchChanged(s) => self.input.update(s), + Message::ModePicked(mode) => self.mode = mode, + Message::OrderPicked(order) => self.order = order, + Message::AscendingToggled(b) => self.ascending = b, + Message::ExactToggled(b) => self.exact = b, + Message::ShowEntriesPicked(x) => self.limit = x, + Message::Reset => { + let state = std::mem::take(&mut self.state); + *self = Self::new(self.service.clone()); + self.state = state; + } + + Message::PackagePressed(id) => return Some(Event::OpenPackage(id)), + Message::BasePressed(id) => return Some(Event::OpenBase(id)), + Message::URLPressed(url) => return Some(Event::OpenURL(url)), + + Message::Search => { + let search_data = Data { + mode: self.mode.into(), + order: self.order.into(), + search: match self.input.submit() { + Ok(x) => x, + Err(t) => return Some(t.into()), + }, + limit: self.limit.into(), + exact: self.exact, + ascending: self.ascending, + }; + + self.state = State::Searching; + let arc = self.service.clone(); + + return Some( + Task::perform( + async move { + let Some(service) = arc.try_lock() else { + return Err("other search request is being performed".into()); + }; + service.search(search_data).await + }, + |r| Message::RequestResult(Arc::new(r)), + ) + .into(), + ); + } + + Message::RequestResult(r) => match &*r { + Ok(v) => self.state = State::Table(Table(v.clone())), + Err(e) => self.state = State::Error(e.to_string()), + }, + } + + None + } + + pub fn title(&self) -> String { + let errors = [self.input.error(), self.input.warning()]; + let error = errors.into_iter().flatten().next(); + + match &self.state { + State::None => error.map_or_else(|| "Search".into(), Into::into), + State::Searching => "Searching...".into(), + State::Table(_) => "Displaying search results".into(), + State::Error(e) => e.into(), + } + } +} diff --git a/3/coursework/src/src/widget.rs b/3/coursework/src/src/widget.rs index d30c20f..efb94fa 100644 --- a/3/coursework/src/src/widget.rs +++ b/3/coursework/src/src/widget.rs @@ -26,7 +26,7 @@ pub fn scroll<'a, Message: 'a>(content: impl Into>) -> Elem /// Clickable url pub fn url<'a, Message: Clone + 'a>(txt: &impl ToString, msg: Message) -> Element<'a, Message> { - Element::from(mouse_area(text(txt.to_string()).color(color!(0xBB_B6_DF))).on_press(msg)) + Element::from(mouse_area(text(txt.to_string()).color(color!(0x00_BB_FF))).on_press(msg)) } pub mod tip { From f162127cb89e0bc02120755096fc30bafb604d5c Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Wed, 12 Feb 2025 19:01:22 +0200 Subject: [PATCH 12/13] Open links --- 3/coursework/src/Cargo.lock | 37 ++++++++++++++++++++++++++++++++++ 3/coursework/src/Cargo.toml | 1 + 3/coursework/src/src/main.rs | 26 +++++++++++++++++------- 3/coursework/src/src/search.rs | 10 ++++++++- 3/coursework/src/src/widget.rs | 4 ++-- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/3/coursework/src/Cargo.lock b/3/coursework/src/Cargo.lock index e1c9c5b..721c42b 100644 --- a/3/coursework/src/Cargo.lock +++ b/3/coursework/src/Cargo.lock @@ -2075,6 +2075,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itoa" version = "1.0.14" @@ -2717,6 +2736,17 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "orbclient" version = "0.3.48" @@ -2874,6 +2904,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3256,6 +3292,7 @@ version = "0.1.0" dependencies = [ "data", "iced", + "open", "service", "strum", ] diff --git a/3/coursework/src/Cargo.toml b/3/coursework/src/Cargo.toml index e12dad9..27bd69f 100644 --- a/3/coursework/src/Cargo.toml +++ b/3/coursework/src/Cargo.toml @@ -9,6 +9,7 @@ service = { path = "service" } iced = { version = "0.13.1", features = ["tokio", "lazy"] } strum = { version = "0.27.0", features = ["derive"] } +open = "5.3.2" [workspace] resolver = "2" diff --git a/3/coursework/src/src/main.rs b/3/coursework/src/src/main.rs index 0fe0b6d..b286455 100644 --- a/3/coursework/src/src/main.rs +++ b/3/coursework/src/src/main.rs @@ -34,9 +34,11 @@ struct Repository { >, } +#[derive(Default)] enum Screen { Search, // Statistics, + #[default] Authentication, } @@ -76,8 +78,7 @@ impl Repository { Self { main_id, scale_factor: 1.4, - screen: Screen::Search, - + screen: Screen::default(), authenticated: None, @@ -112,7 +113,7 @@ impl Repository { return task.map(Message::Authentecation); } authentication::Event::Authenticated(authenticated) => { - log!("authenticated as {:#?}", authenticated); + log!("authenticated as {:#?}", *authenticated); self.authenticated = Some(authenticated); self.screen = Screen::Search; } @@ -126,13 +127,21 @@ impl Repository { return task.map(Message::Search); } search::Event::OpenPackage(id) => { - log!("opening package {id}") + log!("opening package {id}"); } search::Event::OpenBase(id) => { - log!("opening package base {id}") + log!("opening package base {id}"); } search::Event::OpenURL(url) => { - log!("opening url {url}") + log!("opening url {url}"); + match open::that(url.as_ref()) { + Ok(()) => { + log!("opened url {url}"); + } + Err(e) => { + log!("can't open url \"{url}\": {e}"); + } + } } } } @@ -155,7 +164,10 @@ impl Repository { fn title(&self, _: window::Id) -> String { // "Repository".into() - self.authentication.title() + match self.screen { + Screen::Search => self.search.title(), + Screen::Authentication => self.authentication.title(), + } } fn subscription(&self) -> Subscription { diff --git a/3/coursework/src/src/search.rs b/3/coursework/src/src/search.rs index bb84c15..3ddaa7f 100644 --- a/3/coursework/src/src/search.rs +++ b/3/coursework/src/src/search.rs @@ -158,7 +158,15 @@ impl Table { entry .url .as_ref() - .map_or("-".into(), |s| url(&"link", Message::URLPressed(s.clone()))), + .map_or("-".into(), |s| + + tip( + url(&"link", Message::URLPressed(s.clone())), + s.clone(), + tip::Position::Bottom, + ), + + ), ); table[4].push(text(entry.description.to_string()).into()); table[5].push(text(entry.updated_at.to_string()).into()); diff --git a/3/coursework/src/src/widget.rs b/3/coursework/src/src/widget.rs index efb94fa..f064317 100644 --- a/3/coursework/src/src/widget.rs +++ b/3/coursework/src/src/widget.rs @@ -36,12 +36,12 @@ pub mod tip { /// Tooltip with some styling applied pub fn tip<'a, Message: 'a>( content: impl Into>, - tip: &'a str, + tip: impl ToString, position: tip::Position, ) -> Element<'a, Message> { tooltip( content, - container(text(tip).size(14)) + container(text(tip.to_string()).size(14)) .padding(5) .style(container::dark), position, From e6b4d43fc72d8a6f7bb2ba2328618648420c3c1f Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Wed, 12 Feb 2025 19:07:41 +0200 Subject: [PATCH 13/13] Update LICENSE --- 3/coursework/src/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3/coursework/src/LICENSE b/3/coursework/src/LICENSE index a669780..e8e16d9 100644 --- a/3/coursework/src/LICENSE +++ b/3/coursework/src/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 NUREH8 +Copyright (c) 2025 Anton Bilous Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: