Working Login & Register functionality
This commit is contained in:
217
src/Cargo.lock
generated
217
src/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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"]
|
||||
|
@ -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,
|
||||
|
12
src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json
generated
Normal file
12
src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json
generated
Normal file
@ -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"
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_base",
|
||||
"name": "base",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
|
12
src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json
generated
Normal file
12
src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json
generated
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "UPDATE Packages SET base = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b"
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_base",
|
||||
"name": "base",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "UPDATE Packages SET package_base = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"] }
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! `MySQL` adapters.
|
||||
pub mod base;
|
||||
pub mod package;
|
||||
pub mod search;
|
||||
pub mod user;
|
||||
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for BaseAdapter
|
||||
impl<E> crate::port::Crud<E> for BaseAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for PackageAdapter
|
||||
impl<E> crate::port::Crud<E> 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,
|
||||
|
152
src/data/src/adapter/mysql/search.rs
Normal file
152
src/data/src/adapter/mysql/search.rs
Normal file
@ -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<E> SearchRepository<E> for UserAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
async fn search(connection: &E, data: Data) -> Result<Vec<Entry>> {
|
||||
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(())
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for UserAdapter
|
||||
impl<E> crate::port::Crud<E> for UserAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
|
@ -10,12 +10,13 @@ pub type Result<T = (), E = BoxDynError> = std::result::Result<T, E>;
|
||||
|
||||
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::*;
|
||||
|
||||
|
@ -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<C> {
|
||||
pub trait Crud<C> {
|
||||
type New;
|
||||
type Unique;
|
||||
type Update;
|
||||
@ -16,23 +17,27 @@ pub trait CRUD<C> {
|
||||
fn create(
|
||||
connection: &mut C,
|
||||
data: Self::New,
|
||||
) -> impl Future<Output = crate::Result<Self::Existing>> + Send;
|
||||
) -> impl Future<Output = Result<Self::Existing>> + Send;
|
||||
fn read(
|
||||
connection: &C,
|
||||
data: Self::Unique,
|
||||
) -> impl Future<Output = crate::Result<Option<Self::Existing>>> + Send;
|
||||
) -> impl Future<Output = Result<Option<Self::Existing>>> + Send;
|
||||
fn update(
|
||||
connection: &mut C,
|
||||
existing: &mut Self::Existing,
|
||||
data: Self::Update,
|
||||
) -> impl Future<Output = crate::Result> + Send;
|
||||
fn delete(connection: &mut C, data: Self::Unique)
|
||||
-> impl Future<Output = crate::Result> + Send;
|
||||
) -> impl Future<Output = Result> + Send;
|
||||
fn delete(connection: &mut C, data: Self::Unique) -> impl Future<Output = Result> + 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<String> {
|
||||
}
|
||||
}
|
||||
|
||||
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<T>: Validatable
|
||||
where
|
||||
T: CharLength + Into<Self::Inner>,
|
||||
{
|
||||
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<Self, (T, BoxDynError)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match Self::valid(&value) {
|
||||
Ok(()) => Ok(Self::encapsulate(value.into())),
|
||||
Err(e) => Err((value, e.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Validation<U> for T
|
||||
where
|
||||
T: Validatable,
|
||||
U: CharLength + Into<T::Inner>,
|
||||
{
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use super::MaxLength;
|
||||
use super::Validatable;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait BaseRepository<C>:
|
||||
super::CRUD<C, New = New, Unique = u64, Update = Field, Existing = Base>
|
||||
super::Crud<C, New = New, Unique = u64, Update = Field, Existing = Base>
|
||||
{
|
||||
}
|
||||
|
||||
@ -13,33 +13,21 @@ pub trait BaseRepository<C>:
|
||||
|
||||
#[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<String> for Name {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::validate(&value)?;
|
||||
Ok(Self(value))
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Description(Option<String>);
|
||||
impl MaxLength for Description {
|
||||
impl Validatable for Description {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 510;
|
||||
}
|
||||
impl TryFrom<Option<String>> for Description {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<C>:
|
||||
super::CRUD<C, New = New, Update = Field, Unique = Unique, Existing = Package>
|
||||
super::Crud<C, New = New, Update = Field, Unique = Unique, Existing = Package>
|
||||
{
|
||||
}
|
||||
|
||||
#[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<String> for Name {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<String> for Version {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<String>);
|
||||
impl MaxLength for Description {
|
||||
impl Validatable for Description {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 255;
|
||||
}
|
||||
impl TryFrom<Option<String>> for Description {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
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<String>);
|
||||
impl MaxLength for URL {
|
||||
pub struct Url(Option<String>);
|
||||
impl Validatable for Url {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 510;
|
||||
}
|
||||
impl TryFrom<Option<String>> for URL {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
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<DateTime<Utc>>),
|
||||
CreatedAt(DateTime<Utc>),
|
||||
UpdatedAt(DateTime<Utc>),
|
||||
@ -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<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
@ -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
|
||||
|
69
src/data/src/port/search.rs
Normal file
69
src/data/src/port/search.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use super::Validatable;
|
||||
use crate::Result;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait SearchRepository<C> {
|
||||
fn search(connection: &C, data: Data) -> impl Future<Output = Result<Vec<Entry>>> + 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<str>,
|
||||
pub version: Box<str>,
|
||||
pub base_id: u64,
|
||||
pub base_name: Box<str>,
|
||||
pub url: Option<Box<str>>,
|
||||
pub description: Box<str>,
|
||||
pub submitter_id: u64,
|
||||
pub submitter_name: Box<str>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
@ -1,61 +1,40 @@
|
||||
use super::MaxLength;
|
||||
use super::Validatable;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait UserRepository<C>:
|
||||
super::CRUD<C, New = New, Update = Field, Unique = Unique, Existing = User>
|
||||
super::Crud<C, New = New, Update = Field, Unique = Unique, Existing = User>
|
||||
{
|
||||
}
|
||||
|
||||
#[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<String> for Name {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<String> for Email {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<String> for Password {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct User {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) name: String,
|
||||
|
@ -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"
|
||||
|
@ -11,6 +11,7 @@ where
|
||||
UR: UserRepository<C> + Sync,
|
||||
{
|
||||
driver: D,
|
||||
// connection: Option<C>,
|
||||
_user_repository: PhantomData<UR>,
|
||||
}
|
||||
|
||||
@ -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<D, C, UR> AuthenticationRepository for AuthenticationAdapter<D, C, UR>
|
||||
where
|
||||
C: Send,
|
||||
C: Send, //+ Sync,
|
||||
D: Connect<Connection = C> + Sync,
|
||||
UR: UserRepository<C> + Sync,
|
||||
{
|
||||
|
@ -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<T = (), E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub trait AuthenticationContract {
|
||||
pub trait AuthenticationContract: Send {
|
||||
fn name_available(&self, name: Name) -> impl Future<Output = Result> + Send;
|
||||
fn email_available(&self, email: Email) -> impl Future<Output = Result> + 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 = String> = (T, BoxDynError);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Login {
|
||||
Name(Name),
|
||||
Email(Email),
|
||||
}
|
||||
impl AsRef<str> for Login {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::Name(name) => name.as_ref(),
|
||||
Self::Email(email) => email.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for Login {
|
||||
type Error = (String, &'static str);
|
||||
type Error = ReturnError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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<Login> 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<str> for Name {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for Name {
|
||||
type Error = (String, Box<dyn std::error::Error>);
|
||||
type Error = ReturnError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
#[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<str> for Email {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for Email {
|
||||
type Error = (String, Box<dyn std::error::Error>);
|
||||
type Error = ReturnError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
#[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<str> for Password {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for Password {
|
||||
type Error = (String, &'static str);
|
||||
type Error = ReturnError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -4,3 +4,5 @@ pub use authentication::{
|
||||
Authenticated, AuthenticationAdapter, AuthenticationContract, AuthenticationRepository,
|
||||
AuthenticationService,
|
||||
};
|
||||
|
||||
// pub
|
||||
|
171
src/src/input.rs
Normal file
171
src/src/input.rs
Normal file
@ -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<T> {
|
||||
id: &'static str,
|
||||
value: Value<T>,
|
||||
warning: Option<String>,
|
||||
}
|
||||
|
||||
// use std::ops::Deref;
|
||||
// impl<T> Deref for Input<T> {
|
||||
// type Target = Value<T>;
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// &self.value
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T: AsRef<str>> AsRef<str> for Input<T> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.value.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Input<T> {
|
||||
pub const fn new(id: &'static str) -> Self {
|
||||
Self {
|
||||
id,
|
||||
value: Value::None,
|
||||
warning: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus<Message>(&self) -> iced::Task<Message> {
|
||||
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<T>) {
|
||||
self.value = value;
|
||||
}
|
||||
pub fn set_warning(&mut self, value: &impl ToString) {
|
||||
self.warning = Some(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Input<T> {
|
||||
pub fn set_error(&mut self, value: &impl ToString) {
|
||||
self.value.set_error(value.to_string());
|
||||
}
|
||||
|
||||
pub fn view<Message: Clone>(&self, placeholder: &str) -> TextInput<Message> {
|
||||
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<T, E> Input<T>
|
||||
where
|
||||
E: ToString,
|
||||
T: TryFrom<String, Error = (String, E)>,
|
||||
{
|
||||
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<T, E> Input<T>
|
||||
where
|
||||
E: ToString,
|
||||
T: TryFrom<String, Error = (String, E)> + Clone,
|
||||
{
|
||||
pub fn submit<Message>(&mut self) -> Result<T, iced::Task<Message>> {
|
||||
match self.value() {
|
||||
Ok(x) => Ok(x.clone()),
|
||||
Err(_) => Err(self.focus()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum Value<T> {
|
||||
#[default]
|
||||
None,
|
||||
Valid(T),
|
||||
Invalid {
|
||||
value: String,
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> AsRef<str> for Value<T> {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::None => "",
|
||||
Self::Valid(x) => x.as_ref(),
|
||||
Self::Invalid { value, .. } => value.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Value<T> {
|
||||
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<T, E> Value<T>
|
||||
where
|
||||
E: ToString,
|
||||
T: TryFrom<String, Error = (String, E)>,
|
||||
{
|
||||
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(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
177
src/src/login.rs
Normal file
177
src/src/login.rs
Normal file
@ -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<S> {
|
||||
login: Input<authentication::Login>,
|
||||
password: Input<authentication::Password>,
|
||||
show_password: bool,
|
||||
|
||||
state: State,
|
||||
service: Arc<Mutex<S>>,
|
||||
}
|
||||
enum State {
|
||||
None,
|
||||
Requesting,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
SwitchToRegister,
|
||||
Task(Task<Message>),
|
||||
Authenticated(Authenticated),
|
||||
}
|
||||
impl From<Task<Message>> for Event {
|
||||
fn from(value: Task<Message>) -> Self {
|
||||
Self::Task(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
LoginChanged(String),
|
||||
PasswordChanged(String),
|
||||
ShowPasswordToggled(bool),
|
||||
|
||||
LoginSubmitted,
|
||||
PasswordSubmitted,
|
||||
|
||||
LoginPressed,
|
||||
RegisterPressed,
|
||||
|
||||
RequestResult(Arc<Result<Authenticated>>),
|
||||
}
|
||||
|
||||
impl<S: AuthenticationContract + 'static> Login<S> {
|
||||
pub fn new(service: Arc<Mutex<S>>) -> 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<Event> {
|
||||
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<Message> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
184
src/src/main.rs
Normal file
184
src/src/main.rs
Normal file
@ -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<AuthenticationService<AuthenticationAdapter<MySqlPool, SqlxPool, MySqlUserAdapter>>>,
|
||||
register: Register<
|
||||
AuthenticationService<AuthenticationAdapter<MySqlPool, SqlxPool, MySqlUserAdapter>>,
|
||||
>,
|
||||
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<Message>) {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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
|
||||
}
|
||||
}
|
@ -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<S> {
|
||||
name: Input<Name>,
|
||||
email: Input<Email>,
|
||||
password: Input<Password>,
|
||||
repeat: Input<Password>,
|
||||
show_password: bool,
|
||||
|
||||
state: State,
|
||||
service: Arc<Mutex<S>>,
|
||||
}
|
||||
enum State {
|
||||
None,
|
||||
@ -18,25 +27,15 @@ enum State {
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub enum Request {
|
||||
pub enum Event {
|
||||
SwitchToLogin,
|
||||
SimpleValidation(Field),
|
||||
Task(Task<Message>),
|
||||
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<Task<Message>> for Event {
|
||||
fn from(value: Task<Message>) -> Self {
|
||||
Self::Task(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -54,98 +53,110 @@ pub enum Message {
|
||||
|
||||
RegisterPressed,
|
||||
LoginPressed,
|
||||
|
||||
RequestResult(Arc<Result<Authenticated>>),
|
||||
}
|
||||
|
||||
impl Default for Register {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub const fn new() -> Self {
|
||||
impl<S: AuthenticationContract + 'static> Register<S> {
|
||||
pub fn new(service: Arc<Mutex<S>>) -> 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<Request> {
|
||||
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<Event> {
|
||||
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 {
|
||||
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();
|
||||
|
||||
Request::Register {
|
||||
name: self.name.value().into(),
|
||||
email: self.email.value().into(),
|
||||
password: self.password.value().into(),
|
||||
}
|
||||
}
|
||||
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<Message> {
|
||||
@ -194,7 +205,7 @@ impl Register {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn title(&self) -> std::borrow::Cow<str> {
|
||||
pub fn title(&self) -> String {
|
||||
let errors = [
|
||||
self.name.error(),
|
||||
self.email.error(),
|
@ -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>>) -> Element<'a, Message> {
|
||||
Scrollable::with_direction(content, scrollable::Direction::Both {
|
||||
Scrollable::with_direction(
|
||||
content,
|
||||
scrollable::Direction::Both {
|
||||
vertical: scrollable::Scrollbar::default(),
|
||||
horizontal: scrollable::Scrollbar::default(),
|
||||
})
|
||||
},
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "view"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.13.1", features = ["lazy", "tokio"] }
|
@ -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<Message>),
|
||||
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<Request> {
|
||||
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<Message> {
|
||||
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<str> {
|
||||
match self.screen {
|
||||
Screen::Login => self.login.title(),
|
||||
Screen::Register => self.register.title(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Message>),
|
||||
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<Request> {
|
||||
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<Message> {
|
||||
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<str> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String>,
|
||||
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<String, &str> {
|
||||
// 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<Message>(&self) -> iced::Task<Message> {
|
||||
iced::widget::text_input::focus(self.id)
|
||||
}
|
||||
pub fn view<Message: Clone>(&self, placeholder: &str) -> TextInput<Message> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
mod widget;
|
||||
|
||||
mod input;
|
||||
pub use input::Validation;
|
||||
|
||||
pub mod authentication;
|
||||
pub use authentication::{Authentication, login, register};
|
Reference in New Issue
Block a user