Compare commits
32 commits
graphql-in
...
main
Author | SHA1 | Date | |
---|---|---|---|
443c7a7e0c | |||
4bfd47d8c4 | |||
94ed4c46b9 | |||
25b3128181 | |||
23cd674e31 | |||
af921088f9 | |||
a40e9c98c4 | |||
2d072f3303 | |||
792b4daf04 | |||
9eb930da19 | |||
e0c49ffa86 | |||
1318319ad1 | |||
ac8eb8118d | |||
4ff9928a94 | |||
68b458df5e | |||
afa3bd3ece | |||
c38f19d76c | |||
49315128f8 | |||
2455619735 | |||
3fa56abc83 | |||
e0e5fc49c2 | |||
c1207f613d | |||
fc13b0dae6 | |||
3ed12c80a6 | |||
0ac35c3ca5 | |||
e46b693e56 | |||
291cb05847 | |||
af3f89c561 | |||
5bd19803fe | |||
dc70316bae | |||
4760364491 | |||
c8f32d027f |
12 changed files with 964 additions and 286 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -16,3 +16,6 @@ playwright/.cache/
|
|||
.sass-cache/
|
||||
|
||||
.idea/
|
||||
|
||||
# Ignore database file
|
||||
compareware.db
|
283
Cargo.lock
generated
283
Cargo.lock
generated
|
@ -8,7 +8,7 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.6.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
@ -29,7 +29,7 @@ dependencies = [
|
|||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"bitflags",
|
||||
"bitflags 2.6.0",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"futures-core",
|
||||
|
@ -52,9 +52,9 @@ dependencies = [
|
|||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"ahash",
|
||||
"ahash 0.8.11",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bitflags 2.6.0",
|
||||
"brotli",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
|
@ -127,7 +127,7 @@ dependencies = [
|
|||
"actix-utils",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -169,7 +169,7 @@ dependencies = [
|
|||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web-codegen",
|
||||
"ahash",
|
||||
"ahash 0.8.11",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
|
@ -233,6 +233,17 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
|
@ -276,6 +287,21 @@ version = "0.2.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.94"
|
||||
|
@ -398,7 +424,7 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -510,6 +536,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
|
@ -670,6 +702,20 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
|
@ -720,6 +766,7 @@ version = "0.1.3"
|
|||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"gloo-net 0.5.0",
|
||||
|
@ -729,12 +776,16 @@ dependencies = [
|
|||
"leptos_actix",
|
||||
"leptos_meta",
|
||||
"leptos_router",
|
||||
"mio 0.8.11",
|
||||
"nostr-sdk",
|
||||
"paste",
|
||||
"rusqlite",
|
||||
"secp256k1 0.30.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.9",
|
||||
"tokio",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -811,6 +862,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
|
@ -982,6 +1039,18 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.35"
|
||||
|
@ -1227,6 +1296,15 @@ dependencies = [
|
|||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
|
@ -1239,7 +1317,7 @@ version = "0.14.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.8.11",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
|
@ -1254,6 +1332,15 @@ dependencies = [
|
|||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex-conservative"
|
||||
version = "0.1.2"
|
||||
|
@ -1333,6 +1420,29 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
|
@ -1810,6 +1920,16 @@ version = "0.2.168"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linear-map"
|
||||
version = "1.2.0"
|
||||
|
@ -1937,6 +2057,18 @@ dependencies = [
|
|||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
|
@ -2052,6 +2184,15 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.5"
|
||||
|
@ -2109,7 +2250,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2380,7 +2521,7 @@ version = "0.5.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2447,6 +2588,21 @@ dependencies = [
|
|||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"memchr",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -2990,7 +3146,7 @@ dependencies = [
|
|||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
|
@ -3245,6 +3401,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
|
@ -3284,6 +3446,12 @@ version = "0.15.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
@ -3414,13 +3582,31 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3429,7 +3615,22 @@ version = "0.59.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3438,28 +3639,46 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
@ -3472,24 +3691,48 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
|
@ -15,7 +15,9 @@ leptos = { version = "0.6" }
|
|||
leptos_meta = { version = "0.6" }
|
||||
leptos_actix = { version = "0.6", optional = true }
|
||||
leptos_router = { version = "0.6" }
|
||||
paste = "1.0"
|
||||
wasm-bindgen = "=0.2.99"
|
||||
rusqlite = { version = "0.27.0", optional = true}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
web-sys = { version = "0.3", features = ["Event"] }
|
||||
|
@ -28,8 +30,12 @@ wasm-bindgen-futures = "0.4"
|
|||
serde_json="1.0.133"
|
||||
thiserror = "2.0.9"
|
||||
zerofrom = "0.1"
|
||||
mio = "0.8"
|
||||
chrono = "0.4"
|
||||
urlencoding = "2.1.2"
|
||||
|
||||
[features]
|
||||
default = ["ssr"]
|
||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
|
@ -39,6 +45,7 @@ ssr = [
|
|||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:rusqlite"
|
||||
]
|
||||
|
||||
# Override secp256k1's default features
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.83.0"
|
||||
channel = "1.82.0"
|
||||
targets = [ "wasm32-unknown-unknown" ]
|
65
src/api.rs
Normal file
65
src/api.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
use actix_web::{web, HttpResponse};
|
||||
#[cfg(feature = "ssr")]
|
||||
use crate::db::{Database, DbItem};
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "ssr")]
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn get_items(db: web::Data<Arc<Mutex<Database>>>) -> HttpResponse {
|
||||
let db = db.lock().await;
|
||||
match db.get_items().await {
|
||||
Ok(items) => HttpResponse::Ok().json(items),
|
||||
Err(err) => {
|
||||
leptos::logging::error!("Failed to fetch items: {:?}", err);
|
||||
HttpResponse::InternalServerError().body("Failed to fetch items")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn create_item(
|
||||
db: web::Data<Arc<Mutex<Database>>>,
|
||||
item: web::Json<DbItem>,
|
||||
) -> HttpResponse {
|
||||
let db = db.lock().await;
|
||||
match db.insert_item(&item.into_inner()).await {
|
||||
Ok(_) => HttpResponse::Ok().body("Item inserted"),
|
||||
Err(err) => {
|
||||
leptos::logging::error!("Failed to insert item: {:?}", err);
|
||||
HttpResponse::InternalServerError().body("Failed to insert item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn delete_item(
|
||||
db: web::Data<Arc<Mutex<Database>>>,
|
||||
item_id: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let db = db.lock().await;
|
||||
match db.delete_item(&item_id).await {
|
||||
Ok(_) => HttpResponse::Ok().body("Item deleted"),
|
||||
Err(err) => {
|
||||
leptos::logging::error!("Failed to delete item: {:?}", err);
|
||||
HttpResponse::InternalServerError().body("Failed to delete item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn delete_property(
|
||||
db: web::Data<Arc<Mutex<Database>>>,
|
||||
property: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let db = db.lock().await;
|
||||
match db.delete_property(&property).await {
|
||||
Ok(_) => HttpResponse::Ok().body("Property deleted"),
|
||||
Err(err) => {
|
||||
leptos::logging::error!("Failed to delete property: {:?}", err);
|
||||
HttpResponse::InternalServerError().body("Failed to delete property")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
use leptos::*;
|
||||
use leptos_dom::ev::SubmitEvent;
|
||||
use leptos::logging::log;
|
||||
|
||||
#[component]
|
||||
pub fn ItemForm(on_submit: Box<dyn Fn(String, String, Vec<(String, String)>, String, u8)>) -> impl IntoView {
|
||||
let (name, set_name) = create_signal(String::new());
|
||||
let (description, set_description) = create_signal(String::new());
|
||||
let (tags, set_tags) = create_signal(Vec::<(String, String)>::new());
|
||||
let (tag_key, set_tag_key) = create_signal(String::new());
|
||||
let (tag_value, set_tag_value) = create_signal(String::new());
|
||||
let (review, set_review) = create_signal(String::new());
|
||||
let (rating, set_rating) = create_signal(5u8); // Default rating to 5
|
||||
|
||||
let add_tag = move |_| {
|
||||
if !tag_key.get().is_empty() && !tag_value.get().is_empty() {
|
||||
set_tags.update(|t| t.push((tag_key.get(), tag_value.get())));
|
||||
set_tag_key.set(String::new());
|
||||
set_tag_value.set(String::new());
|
||||
}
|
||||
};
|
||||
|
||||
let handle_submit = move |ev: SubmitEvent| {
|
||||
ev.prevent_default();
|
||||
|
||||
// Validation
|
||||
if name.get().is_empty() || description.get().is_empty() || rating.get() < 1 || rating.get() > 5 {
|
||||
log!("Validation failed: Check required fields.");
|
||||
return;
|
||||
}
|
||||
|
||||
on_submit(
|
||||
name.get(),
|
||||
description.get(),
|
||||
tags.get().clone(),
|
||||
review.get(),
|
||||
rating.get(),
|
||||
);
|
||||
|
||||
// Reset values
|
||||
set_name.set(String::new());
|
||||
set_description.set(String::new());
|
||||
set_tags.set(vec![]);
|
||||
set_review.set(String::new());
|
||||
set_rating.set(5);
|
||||
};
|
||||
|
||||
view! {
|
||||
<form on:submit=handle_submit>
|
||||
<input type="text" placeholder="Name" on:input=move |e| set_name.set(event_target_value(&e)) />
|
||||
<textarea placeholder="Description" on:input=move |e| set_description.set(event_target_value(&e)) />
|
||||
<h3>{ "Add Tags" }</h3>
|
||||
<input type="text" placeholder="Key" on:input=move |e| set_tag_key.set(event_target_value(&e)) />
|
||||
<input type="text" placeholder="Value" on:input=move |e| set_tag_value.set(event_target_value(&e)) />
|
||||
<button type="button" on:click=add_tag>{ "Add Tag" }</button>
|
||||
<ul>
|
||||
{tags.get().iter().map(|(key, value)| view! {
|
||||
<li>{ format!("{}: {}", key, value) }</li>
|
||||
}).collect::<Vec<_>>() }
|
||||
</ul>
|
||||
<h3>{ "Write a Review" }</h3>
|
||||
<textarea placeholder="Review" on:input=move |e| set_review.set(event_target_value(&e)) />
|
||||
<h3>{ "Rating (1-5)" }</h3>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="5"
|
||||
value={rating.get()}
|
||||
on:input=move |e| set_rating.set(event_target_value(&e).parse::<u8>().unwrap_or(5))
|
||||
/>
|
||||
<button type="submit">{ "Add Item" }</button>
|
||||
</form>
|
||||
}
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
use crate::components::editable_cell::EditableCell;
|
||||
use crate::components::editable_cell::InputType;
|
||||
use leptos::*;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use leptos::logging::log;
|
||||
use crate::models::item::Item;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::JsCast;
|
||||
use urlencoding::encode;
|
||||
use gloo_net::http::Request;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
struct WikidataSuggestion {
|
||||
|
@ -16,25 +19,97 @@ struct WikidataSuggestion {
|
|||
description: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DbItem {
|
||||
id: String,
|
||||
name: String,
|
||||
description: String,
|
||||
wikidata_id: Option<String>,
|
||||
custom_properties: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ItemsList(
|
||||
items: ReadSignal<Vec<Item>>,
|
||||
set_items: WriteSignal<Vec<Item>>,
|
||||
) -> impl IntoView {
|
||||
// State to track selected properties
|
||||
let (selected_properties, set_selected_properties) = create_signal(HashMap::<String, bool>::new());
|
||||
|
||||
// State to track the currently focused cell
|
||||
let (focused_cell, set_focused_cell) = create_signal(None::<String>);
|
||||
|
||||
// State to manage dynamic property names
|
||||
let (custom_properties, set_custom_properties) = create_signal(Vec::<String>::new());
|
||||
|
||||
// state to manage suggestions visibility
|
||||
// State to manage suggestions visibility
|
||||
let (show_suggestions, set_show_suggestions) = create_signal(HashMap::<String, bool>::new());
|
||||
|
||||
// cache to store fetched properties
|
||||
let (fetched_properties, set_fetched_properties) = create_signal(HashMap::<String, String>::new());
|
||||
let (fetched_properties, set_fetched_properties) = create_signal(HashMap::<String, HashMap<String, String>>::new());
|
||||
|
||||
//signal to store the fetched property labels
|
||||
// Signal to store the fetched property labels
|
||||
let (property_labels, set_property_labels) = create_signal(HashMap::<String, String>::new());
|
||||
|
||||
spawn_local(async move {
|
||||
match load_items_from_db().await {
|
||||
Ok(loaded_items) => {
|
||||
// Set the loaded items
|
||||
if loaded_items.is_empty() {
|
||||
// Initialize with one empty item if the database is empty
|
||||
set_items.set(vec![Item {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
wikidata_id: None,
|
||||
custom_properties: HashMap::new(),
|
||||
}]);
|
||||
} else {
|
||||
set_items.set(loaded_items.clone());
|
||||
}
|
||||
|
||||
// Derive selected properties from the loaded items
|
||||
let mut selected_props = HashMap::new();
|
||||
let loaded_items_clone = loaded_items.clone();
|
||||
for item in loaded_items {
|
||||
for (property, _) in item.custom_properties {
|
||||
selected_props.insert(property, true);
|
||||
}
|
||||
}
|
||||
set_selected_properties.set(selected_props);
|
||||
|
||||
// Update the custom_properties signal
|
||||
let mut custom_props = Vec::new();
|
||||
for item in loaded_items_clone {
|
||||
for (property, _) in &item.custom_properties {
|
||||
if !custom_props.iter().any(|p| p == property) {
|
||||
custom_props.push(property.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let custom_props_clone = custom_props.clone();
|
||||
set_custom_properties.set(custom_props);
|
||||
|
||||
// Fetch labels for the custom properties
|
||||
let property_ids = custom_props_clone;
|
||||
let labels = fetch_property_labels(property_ids).await;
|
||||
set_property_labels.update(|labels_map| {
|
||||
for (key, value) in labels {
|
||||
labels_map.insert(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
// log!("Items after loading: {:?}", items.get());
|
||||
}
|
||||
Err(err) => {
|
||||
log!("Error loading items: {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Ensure there's an initial empty row
|
||||
if items.get().is_empty() {
|
||||
set_items.set(vec![Item {
|
||||
|
@ -47,9 +122,158 @@ pub fn ItemsList(
|
|||
}]);
|
||||
}
|
||||
|
||||
// Function to send an item to the backend API
|
||||
async fn save_item_to_db(item: Item, selected_properties: ReadSignal<HashMap<String, bool>>) {
|
||||
// Use a reactive closure to access `selected_properties`
|
||||
let custom_properties: HashMap<String, String> = (move || {
|
||||
let selected_props = selected_properties.get(); // Access the signal inside a reactive closure
|
||||
item.custom_properties
|
||||
.into_iter()
|
||||
.filter(|(key, _)| selected_props.contains_key(key)) // Use the extracted value
|
||||
.collect()
|
||||
})();
|
||||
|
||||
// Serialize `custom_properties` to a JSON string
|
||||
let custom_properties = serde_json::to_string(&custom_properties).unwrap();
|
||||
|
||||
// Create a new struct to send to the backend
|
||||
#[derive(Serialize, Debug)]
|
||||
struct ItemToSend {
|
||||
id: String,
|
||||
name: String,
|
||||
description: String,
|
||||
wikidata_id: Option<String>,
|
||||
custom_properties: String, // JSON-encoded string
|
||||
}
|
||||
|
||||
let item_to_send = ItemToSend {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
wikidata_id: item.wikidata_id,
|
||||
custom_properties, // Use the serialized string
|
||||
};
|
||||
|
||||
let response = gloo_net::http::Request::post("/api/items")
|
||||
.json(&item_to_send)
|
||||
.unwrap()
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(resp) => {
|
||||
if resp.status() == 200 {
|
||||
// log!("Item saved to database: {:?}", item_to_send);
|
||||
} else {
|
||||
log!("Failed to save item: {}", resp.status_text());
|
||||
}
|
||||
}
|
||||
Err(err) => log!("Failed to save item: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
//function to load items from database
|
||||
async fn load_items_from_db() -> Result<Vec<Item>, String> {
|
||||
let response = gloo_net::http::Request::get("/api/items")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| format!("Failed to fetch items: {:?}", err))?;
|
||||
|
||||
if response.status() == 200 {
|
||||
// Deserialize into Vec<DbItem>
|
||||
log!("Loading items from DB...");
|
||||
let db_items = response
|
||||
.json::<Vec<DbItem>>()
|
||||
.await
|
||||
.map_err(|err| format!("Failed to parse items: {:?}", err))?;
|
||||
|
||||
// log!("Deserialized DB items: {:?}", db_items);
|
||||
|
||||
// Convert DbItem to Item
|
||||
let items = db_items
|
||||
.into_iter()
|
||||
.map(|db_item| {
|
||||
// Deserialize `custom_properties` from a JSON string to a HashMap
|
||||
let custom_properties: HashMap<String, String> =
|
||||
serde_json::from_str(&db_item.custom_properties)
|
||||
.unwrap_or_default(); // Fallback to an empty HashMap if deserialization fails
|
||||
|
||||
log!("Loaded item: {:?}", db_item.id);
|
||||
log!("Custom properties: {:?}", custom_properties);
|
||||
|
||||
Item {
|
||||
id: db_item.id,
|
||||
name: db_item.name,
|
||||
description: db_item.description,
|
||||
wikidata_id: db_item.wikidata_id,
|
||||
custom_properties, // Deserialized HashMap
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// log!("Converted items: {:?}", items);
|
||||
Ok(items)
|
||||
} else {
|
||||
Err(format!("Failed to fetch items: {}", response.status_text()))
|
||||
}
|
||||
}
|
||||
|
||||
// Function to remove an item
|
||||
let remove_item = move |index: usize| {
|
||||
let item_id = items.get()[index].id.clone();
|
||||
spawn_local(async move {
|
||||
let response = gloo_net::http::Request::delete(&format!("/api/items/{}", item_id))
|
||||
.send()
|
||||
.await;
|
||||
match response {
|
||||
Ok(resp) => {
|
||||
if resp.status() == 200 {
|
||||
set_items.update(|items| {
|
||||
items.remove(index);
|
||||
});
|
||||
log!("Item deleted: {}", item_id);
|
||||
} else {
|
||||
log!("Failed to delete item: {}", resp.status_text());
|
||||
}
|
||||
}
|
||||
Err(err) => log!("Failed to delete item: {:?}", err),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Function to remove a property
|
||||
let remove_property = move |property: String| {
|
||||
spawn_local(async move {
|
||||
let response = gloo_net::http::Request::delete(&format!("/api/properties/{}", property))
|
||||
.send()
|
||||
.await;
|
||||
match response {
|
||||
Ok(resp) => {
|
||||
if resp.status() == 200 {
|
||||
set_custom_properties.update(|props| {
|
||||
props.retain(|p| p != &property);
|
||||
});
|
||||
set_selected_properties.update(|selected| {
|
||||
selected.remove(&property);
|
||||
});
|
||||
set_items.update(|items| {
|
||||
for item in items {
|
||||
item.custom_properties.remove(&property);
|
||||
}
|
||||
});
|
||||
log!("Property deleted: {}", property);
|
||||
} else {
|
||||
log!("Failed to delete property: {}", resp.status_text());
|
||||
}
|
||||
}
|
||||
Err(err) => log!("Failed to delete property: {:?}", err),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// State to store Wikidata suggestions
|
||||
let (wikidata_suggestions, set_wikidata_suggestions) = create_signal(HashMap::<String, Vec<WikidataSuggestion>>::new());
|
||||
|
||||
// Fetch Wikidata suggestions
|
||||
// Function to fetch Wikidata suggestions
|
||||
let fetch_wikidata_suggestions = move |key: String, query: String| {
|
||||
log!("Fetching suggestions for key: {}, query: {}", key, query);
|
||||
spawn_local(async move {
|
||||
|
@ -68,9 +292,7 @@ pub fn ItemsList(
|
|||
match gloo_net::http::Request::get(&url).send().await {
|
||||
Ok(response) => {
|
||||
if let Ok(data) = response.json::<WikidataResponse>().await {
|
||||
log!("Fetching suggestions for key: {}, query: {}", key, query);
|
||||
set_wikidata_suggestions.update(|suggestions| {
|
||||
log!("Updated suggestions: {:?}", suggestions);
|
||||
suggestions.insert(key, data.search);
|
||||
});
|
||||
}
|
||||
|
@ -81,103 +303,172 @@ pub fn ItemsList(
|
|||
};
|
||||
|
||||
//function to fetch properties
|
||||
async fn fetch_item_properties(wikidata_id: &str, set_fetched_properties: WriteSignal<HashMap<String, String>>, set_property_labels: WriteSignal<HashMap<String, String>>,) -> HashMap<String, String> {
|
||||
let url = format!(
|
||||
"https://www.wikidata.org/wiki/Special:EntityData/{}.json",
|
||||
async fn fetch_item_properties(wikidata_id: &str) -> HashMap<String, String> {
|
||||
let sparql_query = format!(
|
||||
r#"
|
||||
SELECT ?propLabel ?value ?valueLabel WHERE {{
|
||||
wd:{} ?prop ?statement.
|
||||
?statement ?ps ?value.
|
||||
?property wikibase:claim ?prop.
|
||||
?property wikibase:statementProperty ?ps.
|
||||
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
|
||||
}}
|
||||
"#,
|
||||
wikidata_id
|
||||
);
|
||||
|
||||
match gloo_net::http::Request::get(&url).send().await {
|
||||
let url = format!(
|
||||
"https://query.wikidata.org/sparql?query={}&format=json",
|
||||
urlencoding::encode(&sparql_query)
|
||||
);
|
||||
|
||||
match gloo_net::http::Request::get(&url)
|
||||
.header("Accept", "application/json")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
if let Ok(data) = response.json::<serde_json::Value>().await {
|
||||
if let Some(entities) = data["entities"].as_object() {
|
||||
if let Some(entity) = entities.get(wikidata_id) {
|
||||
if let Some(claims) = entity["claims"].as_object() {
|
||||
let mut result = HashMap::new();
|
||||
for (property, values) in claims {
|
||||
if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_str() {
|
||||
result.insert(property.clone(), value.to_string());
|
||||
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_object() {
|
||||
result.insert(property.clone(), serde_json::to_string(value).unwrap());
|
||||
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_f64() {
|
||||
result.insert(property.clone(), value.to_string());
|
||||
if let Some(bindings) = data["results"]["bindings"].as_array() {
|
||||
for binding in bindings {
|
||||
let prop_label = binding["propLabel"]["value"].as_str().unwrap_or("").to_string();
|
||||
let prop_label = prop_label.replace("http://www.wikidata.org/prop/", "");
|
||||
let value_label = binding["valueLabel"]["value"].as_str().unwrap_or("").to_string();
|
||||
result.insert(prop_label, value_label);
|
||||
log!("result: {:?}", result);
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
} else {
|
||||
result.insert(property.clone(), "Unsupported data type".to_string());
|
||||
}
|
||||
}
|
||||
// Fetch labels for the properties
|
||||
let property_ids = result.keys().cloned().collect::<Vec<_>>();
|
||||
let labels = fetch_property_labels(property_ids).await;
|
||||
set_property_labels.update(|labels_map| {
|
||||
for (key, value) in labels {
|
||||
labels_map.insert(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Update fetched properties
|
||||
set_fetched_properties.update(|properties| {
|
||||
for (key, val) in result.clone() {
|
||||
properties.insert(key.clone(), val.clone());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => log!("Error fetching item properties: {:?}", err),
|
||||
}
|
||||
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_property_labels(property_ids: Vec<String>) -> HashMap<String, String> {
|
||||
let mut property_labels = HashMap::new();
|
||||
log!("Fetching property labels for properties: {:?}", property_ids);
|
||||
|
||||
// Construct the API URL to fetch labels for multiple properties
|
||||
let url = format!(
|
||||
"https://www.wikidata.org/w/api.php?action=wbgetentities&ids={}&props=labels&format=json&languages=en&origin=*",
|
||||
property_ids.join("|")
|
||||
// Remove the "http://www.wikidata.org/prop/" prefix from property IDs
|
||||
let property_ids: Vec<String> = property_ids
|
||||
.into_iter()
|
||||
.map(|id| id.replace("http://www.wikidata.org/prop/", ""))
|
||||
.collect();
|
||||
|
||||
let property_ids_str = property_ids.join(" wd:");
|
||||
let sparql_query = format!(
|
||||
r#"
|
||||
SELECT ?prop ?propLabel WHERE {{
|
||||
VALUES ?prop {{ wd:{} }}
|
||||
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
|
||||
}}
|
||||
"#,
|
||||
property_ids_str
|
||||
);
|
||||
|
||||
match gloo_net::http::Request::get(&url).send().await {
|
||||
let url = format!(
|
||||
"https://query.wikidata.org/sparql?query={}&format=json",
|
||||
urlencoding::encode(&sparql_query)
|
||||
);
|
||||
log!("Sending request to URL: {}", url);
|
||||
|
||||
match gloo_net::http::Request::get(&url)
|
||||
.header("Accept", "application/json")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
if let Ok(data) = response.json::<serde_json::Value>().await {
|
||||
if let Some(entities) = data["entities"].as_object() {
|
||||
for (property_id, entity) in entities {
|
||||
if let Some(label) = entity["labels"]["en"]["value"].as_str() {
|
||||
property_labels.insert(property_id.clone(), label.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => log!("Error fetching property labels: {:?}", err),
|
||||
log!("Received response from Wikidata. Status: {}", response.status());
|
||||
if response.status() != 200 {
|
||||
log!("Error: Unexpected status code {}", response.status());
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
property_labels
|
||||
match response.text().await {
|
||||
Ok(text) => {
|
||||
log!("Response body: {}", text);
|
||||
match serde_json::from_str::<serde_json::Value>(&text) {
|
||||
Ok(data) => {
|
||||
log!("Successfully parsed response from Wikidata");
|
||||
let mut result = HashMap::new();
|
||||
if let Some(bindings) = data["results"]["bindings"].as_array() {
|
||||
log!("Found {} bindings in response", bindings.len());
|
||||
for (i, binding) in bindings.iter().enumerate() {
|
||||
if let (Some(prop), Some(label)) = (
|
||||
binding["prop"]["value"].as_str(),
|
||||
binding["propLabel"]["value"].as_str()
|
||||
) {
|
||||
let prop_id = prop.split('/').last().unwrap_or("").to_string();
|
||||
result.insert(prop_id.clone(), label.to_string());
|
||||
log!("Processed binding {}: prop_id = {}, label = {}", i, prop_id, label);
|
||||
} else {
|
||||
log!("Warning: Binding {} is missing prop or propLabel", i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log!("Warning: No bindings found in the response");
|
||||
}
|
||||
log!("Fetched {} property labels", result.len());
|
||||
result
|
||||
}
|
||||
Err(e) => {
|
||||
log!("Error parsing response from Wikidata: {:?}", e);
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log!("Error reading response body: {:?}", e);
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log!("Error fetching property labels from Wikidata: {:?}", e);
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new custom property
|
||||
let add_property = move |property: String| {
|
||||
// Normalize the property ID
|
||||
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
||||
|
||||
set_custom_properties.update(|props| {
|
||||
if !props.contains(&property) && !property.is_empty() {
|
||||
props.push(property.clone());
|
||||
if !props.contains(&normalized_property) && !normalized_property.is_empty() {
|
||||
props.push(normalized_property.clone());
|
||||
|
||||
//update the selected_properties state when a new property is added
|
||||
set_selected_properties.update(|selected| {
|
||||
selected.insert(normalized_property.clone(), true);
|
||||
});
|
||||
|
||||
// Ensure the grid updates reactively
|
||||
set_items.update(|items| {
|
||||
for item in items {
|
||||
item.custom_properties.entry(property.clone()).or_insert_with(|| "".to_string());
|
||||
item.custom_properties.entry(normalized_property.clone()).or_insert_with(|| "".to_string());
|
||||
|
||||
// Save the updated item to the database
|
||||
let item_clone = item.clone();
|
||||
spawn_local(async move {
|
||||
save_item_to_db(item_clone, selected_properties).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the property label
|
||||
let property_id = property.clone();
|
||||
let property_id = normalized_property.clone();
|
||||
spawn_local(async move {
|
||||
let labels = fetch_property_labels(vec![property_id.clone()]).await;
|
||||
log!("Fetched labels: {:?}", labels);
|
||||
set_property_labels.update(|labels_map| {
|
||||
if let Some(label) = labels.get(&property_id) {
|
||||
labels_map.insert(property_id, label.clone());
|
||||
for (key, value) in labels {
|
||||
log!("Inserting label: {} -> {}", key, value);
|
||||
labels_map.insert(key, value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -192,8 +483,16 @@ pub fn ItemsList(
|
|||
let set_property_labels = set_property_labels.clone();
|
||||
let property_clone = property.clone();
|
||||
spawn_local(async move {
|
||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
||||
log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
||||
let properties = fetch_item_properties(&wikidata_id).await;
|
||||
// Update fetched properties and property labels
|
||||
set_fetched_properties.update(|fp| {
|
||||
fp.insert(wikidata_id.clone(), properties.clone());
|
||||
});
|
||||
set_property_labels.update(|pl| {
|
||||
for (key, value) in properties.iter() {
|
||||
pl.entry(key.clone()).or_insert_with(|| value.clone());
|
||||
}
|
||||
});
|
||||
if let Some(value) = properties.get(&property_clone) {
|
||||
set_items.update(|items| {
|
||||
if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) {
|
||||
|
@ -223,7 +522,7 @@ pub fn ItemsList(
|
|||
let set_fetched_properties = set_fetched_properties.clone();
|
||||
let set_property_labels = set_property_labels.clone();
|
||||
spawn_local(async move {
|
||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
||||
let properties = fetch_item_properties(&wikidata_id).await;
|
||||
log!("Fetched properties for index {}: {:?}", index, properties);
|
||||
});
|
||||
}
|
||||
|
@ -237,33 +536,36 @@ pub fn ItemsList(
|
|||
item.custom_properties.insert(field.to_string(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the updated item to the database
|
||||
let item_clone = item.clone();
|
||||
spawn_local(async move {
|
||||
save_item_to_db(item_clone, selected_properties).await;
|
||||
});
|
||||
}
|
||||
// Automatically add a new row when editing the last row
|
||||
if index == items.len() - 1 && !value.is_empty() {
|
||||
items.push(Item {
|
||||
let new_item = Item {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
// reviews: vec![],
|
||||
wikidata_id: None,
|
||||
custom_properties: HashMap::new(),
|
||||
};
|
||||
items.push(new_item.clone());
|
||||
|
||||
// Save the new item to the database
|
||||
spawn_local(async move {
|
||||
save_item_to_db(new_item, selected_properties).await;
|
||||
});
|
||||
}
|
||||
log!("Items updated: {:?}", items);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Remove an item
|
||||
let remove_item = move |index: usize| {
|
||||
set_items.update(|items| {
|
||||
items.remove(index);
|
||||
});
|
||||
};
|
||||
|
||||
// List of properties to display as rows
|
||||
let properties = vec!["Name", "Description", "Actions"];
|
||||
let properties = vec!["Name", "Description"];
|
||||
|
||||
view! {
|
||||
<div>
|
||||
|
@ -272,9 +574,12 @@ pub fn ItemsList(
|
|||
<thead>
|
||||
<tr>
|
||||
<th>{ "Property" }</th>
|
||||
{move || items.get().iter().enumerate().map(|(index, _)| {
|
||||
{move || items.get().iter().enumerate().map(|(index, item)| {
|
||||
view! {
|
||||
<th>{ format!("Item {}", index + 1) }</th>
|
||||
<th>
|
||||
{ item.name.clone() }
|
||||
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
|
||||
</th>
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
</tr>
|
||||
|
@ -359,8 +664,8 @@ pub fn ItemsList(
|
|||
let set_fetched_properties = set_fetched_properties.clone();
|
||||
let set_property_labels = set_property_labels.clone();
|
||||
spawn_local(async move {
|
||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
||||
log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
||||
let properties = fetch_item_properties(&wikidata_id).await;
|
||||
// log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
||||
|
||||
// Populate the custom properties for the new item
|
||||
set_items.update(|items| {
|
||||
|
@ -410,9 +715,6 @@ pub fn ItemsList(
|
|||
input_type=InputType::TextArea
|
||||
/>
|
||||
}.into_view(),
|
||||
"Actions" => view! {
|
||||
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
|
||||
}.into_view(),
|
||||
_ => view! {
|
||||
{ "" }
|
||||
}.into_view(),
|
||||
|
@ -427,35 +729,52 @@ pub fn ItemsList(
|
|||
{move || {
|
||||
let custom_props = custom_properties.get().clone();
|
||||
custom_props.into_iter().map(move |property| {
|
||||
let property_clone = property.clone();
|
||||
let property_label = property_labels.get().get(&property_clone).cloned().unwrap_or_else(|| property_clone.clone());
|
||||
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
||||
let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone());
|
||||
log!("Rendering property: {} -> {}", normalized_property, property_label);
|
||||
let property_clone_for_button = normalized_property.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{ property_label }</td>
|
||||
<td>
|
||||
{ property_label }
|
||||
<button class="delete-property" on:click=move |_| {
|
||||
log!("Deleting property: {}", property_clone_for_button);
|
||||
remove_property(property_clone_for_button.clone());
|
||||
set_custom_properties.update(|props| {
|
||||
props.retain(|p| p != &property_clone_for_button);
|
||||
});
|
||||
set_selected_properties.update(|selected| {
|
||||
selected.remove(&property_clone_for_button);
|
||||
});
|
||||
set_items.update(|items| {
|
||||
for item in items {
|
||||
item.custom_properties.remove(&property_clone_for_button);
|
||||
}
|
||||
});
|
||||
}>{ "Delete" }</button>
|
||||
</td>
|
||||
{move || {
|
||||
let property_clone = property_clone.clone(); // Clone `property_clone` again for the inner closure
|
||||
let property_clone_for_cells = normalized_property.clone();
|
||||
items.get().iter().enumerate().map(move |(index, item)| {
|
||||
let property_clone_for_closure = property_clone.clone();
|
||||
let property_clone_for_closure = property_clone_for_cells.clone();
|
||||
view! {
|
||||
<td>
|
||||
<EditableCell
|
||||
value=item.custom_properties.get(&property_clone).cloned().unwrap_or_default()
|
||||
value=item.custom_properties.get(&property_clone_for_closure).cloned().unwrap_or_default()
|
||||
on_input=move |value| update_item(index, &property_clone_for_closure, value)
|
||||
key=Arc::new(format!("custom-{}-{}", property_clone, index))
|
||||
key=Arc::new(format!("custom-{}-{}", property_clone_for_cells, index))
|
||||
focused_cell=focused_cell
|
||||
set_focused_cell=set_focused_cell.clone()
|
||||
on_focus=Some(Callback::new(move |_| {
|
||||
log!("Custom property input focused");
|
||||
}))
|
||||
on_blur=Some(Callback::new(move |_| {
|
||||
log!("Custom property input blurred");
|
||||
}))
|
||||
input_type=InputType::TextArea
|
||||
/>
|
||||
</td>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}}
|
||||
}).collect::<Vec<_>>()}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
|
@ -463,36 +782,33 @@ pub fn ItemsList(
|
|||
</tbody>
|
||||
</table>
|
||||
<div style="margin-bottom: 20px;">
|
||||
<input type="text" id="new-property" placeholder="Add New Property" list="properties"/>
|
||||
<datalist id="properties">
|
||||
{move || {
|
||||
let properties = fetched_properties.get().clone();
|
||||
let property_labels = property_labels.get().clone();
|
||||
properties.into_iter().map(|(key, _)| {
|
||||
let key_clone = key.clone();
|
||||
let label = property_labels.get(&key_clone).cloned().unwrap_or_else(|| key_clone.clone());
|
||||
view! {
|
||||
<option value={format!("{} - {}", key, label)}>{ format!("{} - {}", key, label) }</option>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}}
|
||||
</datalist>
|
||||
<button on:click=move |_| {
|
||||
let property = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("new-property")
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlInputElement>()
|
||||
.unwrap()
|
||||
.value();
|
||||
<input type="text" id="new-property" placeholder="Add New Property" list="properties" on:keydown=move |event| {
|
||||
if event.key() == "Enter"{
|
||||
let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap();
|
||||
let property = input_element.value();
|
||||
if !property.is_empty() {
|
||||
// Extract the coded name from the selected value
|
||||
let coded_name = property.split(" - ").next().unwrap_or(&property).to_string();
|
||||
|
||||
// Add the property using the coded name
|
||||
add_property(coded_name);
|
||||
}>{ "Add Property" }</button>
|
||||
|
||||
// Clear the input field
|
||||
input_element.set_value("");
|
||||
}
|
||||
}
|
||||
} />
|
||||
<datalist id="properties">
|
||||
{move || {
|
||||
let property_labels = property_labels.get().clone();
|
||||
property_labels.into_iter().map(|(property, label)| {
|
||||
let property_clone = property.clone();
|
||||
view! {
|
||||
<option value={property}>{ format!("{} - {}", property_clone, label) }</option>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pub mod item_form;
|
||||
pub mod items_list;
|
||||
pub mod editable_cell;
|
|
@ -1,48 +0,0 @@
|
|||
use leptos::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
struct WikidataResult {
|
||||
id: String,
|
||||
label: String,
|
||||
description: Option<String>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn WikidataLookup(
|
||||
query: String,
|
||||
on_select: impl Fn(WikidataResult) + 'static,
|
||||
) -> impl IntoView {
|
||||
let (suggestions, set_suggestions) = create_signal(Vec::new());
|
||||
|
||||
let fetch_suggestions = move |query: String| {
|
||||
spawn_local(async move {
|
||||
if query.is_empty() {
|
||||
set_suggestions(Vec::new());
|
||||
return;
|
||||
}
|
||||
let url = format!("https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=5&format=json&origin=*", query);
|
||||
if let Ok(response) = reqwest::get(&url).await {
|
||||
if let Ok(data) = response.json::<WikidataResponse>().await {
|
||||
set_suggestions(data.search);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
create_effect(move || {
|
||||
fetch_suggestions(query.clone());
|
||||
});
|
||||
|
||||
view! {
|
||||
<ul>
|
||||
{suggestions.get().iter().map(|suggestion| {
|
||||
view! {
|
||||
<li on:click=move |_| on_select(suggestion.clone())>
|
||||
{format!("{} - {}", suggestion.label, suggestion.description.clone().unwrap_or_default())}
|
||||
</li>
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
</ul>
|
||||
}
|
||||
}
|
136
src/db.rs
Normal file
136
src/db.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
mod db_impl {
|
||||
use rusqlite::{Connection, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use leptos::logging;
|
||||
|
||||
// Define a struct to represent a database connection
|
||||
#[derive(Debug)]
|
||||
pub struct Database {
|
||||
conn: Arc<Mutex<Connection>>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
// Create a new database connection
|
||||
pub fn new(db_path: &str) -> Result<Self, Error> {
|
||||
let conn = Connection::open(db_path)?;
|
||||
logging::log!("Database connection established at: {}", db_path);
|
||||
Ok(Database {
|
||||
conn: Arc::new(Mutex::new(conn)),
|
||||
})
|
||||
}
|
||||
|
||||
// Create the database schema
|
||||
pub async fn create_schema(&self) -> Result<(), Error> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS items (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
wikidata_id TEXT,
|
||||
custom_properties TEXT
|
||||
);",
|
||||
)?;
|
||||
logging::log!("Database schema created or verified");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Insert a new item into the database
|
||||
pub async fn insert_item(&self, item: &DbItem) -> Result<(), Error> {
|
||||
let conn = self.conn.lock().await;
|
||||
let wikidata_id = item.wikidata_id.as_ref().map(|s| s.as_str()).unwrap_or("");
|
||||
conn.execute(
|
||||
"INSERT INTO items (id, name, description, wikidata_id, custom_properties)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
description = excluded.description,
|
||||
wikidata_id = excluded.wikidata_id,
|
||||
custom_properties = excluded.custom_properties;",
|
||||
&[
|
||||
&item.id,
|
||||
&item.name,
|
||||
&item.description,
|
||||
&wikidata_id.to_string(),
|
||||
&item.custom_properties,
|
||||
],
|
||||
)?;
|
||||
logging::log!("Item inserted: {}", item.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_item(&self, item_id: &str) -> Result<(), Error> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute("DELETE FROM items WHERE id = ?", &[item_id])?;
|
||||
logging::log!("Item deleted: {}", item_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_property(&self, property: &str) -> Result<(), Error> {
|
||||
let conn = self.conn.lock().await;
|
||||
let query = format!("UPDATE items SET custom_properties = json_remove(custom_properties, '$.{}')", property);
|
||||
conn.execute(&query, []).map_err(|e| Error::from(e))?;
|
||||
logging::log!("Property deleted: {}", property);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Retrieve all items from the database
|
||||
pub async fn get_items(&self) -> Result<Vec<DbItem>, Error> {
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT * FROM items;")?;
|
||||
let items = stmt.query_map([], |row| {
|
||||
Ok(DbItem {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
description: row.get(2)?,
|
||||
wikidata_id: row.get(3)?,
|
||||
custom_properties: row.get(4)?,
|
||||
})
|
||||
})?;
|
||||
let mut result = Vec::new();
|
||||
for item in items {
|
||||
result.push(item?);
|
||||
}
|
||||
logging::log!("Fetched {} items from the database", result.len()); // Log with Leptos
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
// Define a struct to represent an item in the database
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct DbItem {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub wikidata_id: Option<String>,
|
||||
pub custom_properties: String,
|
||||
}
|
||||
|
||||
// Implement conversion from DbItem to a JSON-friendly format
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct ItemResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub wikidata_id: Option<String>,
|
||||
pub custom_properties: String,
|
||||
}
|
||||
|
||||
impl From<DbItem> for ItemResponse {
|
||||
fn from(item: DbItem) -> Self {
|
||||
ItemResponse {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
wikidata_id: item.wikidata_id,
|
||||
custom_properties: item.custom_properties,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use db_impl::{Database, DbItem, ItemResponse};
|
|
@ -2,6 +2,9 @@ pub mod app;
|
|||
pub mod components;
|
||||
pub mod models;
|
||||
pub mod nostr;
|
||||
pub mod api;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod db;
|
||||
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -6,27 +6,55 @@ async fn main() -> std::io::Result<()> {
|
|||
use leptos::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
use compareware::app::*;
|
||||
use compareware::db::{Database, DbItem};
|
||||
use compareware::api::{get_items, create_item, delete_item, delete_property}; // Import API handlers
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// Load configuration
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
|
||||
// Initialize the database
|
||||
let db = Database::new("compareware.db").unwrap();
|
||||
db.create_schema().await.unwrap(); // Ensure the schema is created
|
||||
let db = Arc::new(Mutex::new(db)); // Wrap the database in an Arc<Mutex<T>> for shared state
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(App);
|
||||
println!("listening on http://{}", &addr);
|
||||
|
||||
// Start the Actix Web server
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let db = db.clone(); // Clone the Arc for each worker
|
||||
|
||||
|
||||
App::new()
|
||||
// serve JS/WASM/CSS from `pkg`
|
||||
// Register custom API routes BEFORE Leptos server functions
|
||||
.service(
|
||||
web::scope("/api")
|
||||
.route("/items", web::get().to(get_items)) // GET /api/items
|
||||
.route("/items", web::post().to(create_item)) // POST /api/items
|
||||
.route("/items/{id}", web::delete().to(delete_item)) // DELETE /api/items/{id}
|
||||
.route("/properties/{property}", web::delete().to(delete_property)), // DELETE /api/properties/{property}
|
||||
)
|
||||
// Register server functions
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
// Serve JS/WASM/CSS from `pkg`
|
||||
.service(Files::new("/pkg", format!("{site_root}/pkg")))
|
||||
// serve other assets from the `assets` directory
|
||||
// Serve other assets from the `assets` directory
|
||||
.service(Files::new("/assets", site_root))
|
||||
// serve the favicon from /favicon.ico
|
||||
// Serve the favicon from /favicon.ico
|
||||
.service(favicon)
|
||||
// Register Leptos routes
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), App)
|
||||
// Pass Leptos options to the app
|
||||
.app_data(web::Data::new(leptos_options.to_owned()))
|
||||
//.wrap(middleware::Compress::default())
|
||||
// Pass the database as shared state
|
||||
.app_data(web::Data::new(db))
|
||||
})
|
||||
.bind(&addr)?
|
||||
.run()
|
||||
|
|
Loading…
Add table
Reference in a new issue