Compare commits
6 Commits
3ba56ca232
...
827809c514
Author | SHA1 | Date | |
---|---|---|---|
|
827809c514 | ||
|
943db3f29f | ||
|
31a961fc3c | ||
|
0db17273df | ||
|
5214b6b88a | ||
|
d3f829880a |
298
backend/Cargo.lock
generated
298
backend/Cargo.lock
generated
@ -68,7 +68,7 @@ dependencies = [
|
|||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash",
|
"ahash",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -281,6 +281,18 @@ version = "0.2.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -302,18 +314,39 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@ -344,6 +377,12 @@ dependencies = [
|
|||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -457,6 +496,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -542,7 +582,10 @@ dependencies = [
|
|||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"argon2",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures",
|
||||||
|
"jsonwebtoken",
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
@ -550,12 +593,65 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -574,8 +670,13 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
@ -598,8 +699,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -874,6 +977,31 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.76"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -979,12 +1107,40 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@ -1023,12 +1179,33 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -1160,6 +1337,21 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
@ -1277,6 +1469,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -1302,12 +1506,24 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.92"
|
version = "2.0.92"
|
||||||
@ -1339,6 +1555,26 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.37"
|
version = "0.3.37"
|
||||||
@ -1448,6 +1684,12 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@ -1504,6 +1746,60 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
@ -13,3 +13,6 @@ actix-files = "0.6"
|
|||||||
actix-cors = "0.6"
|
actix-cors = "0.6"
|
||||||
env_logger = "0.10" # Check for the latest version
|
env_logger = "0.10" # Check for the latest version
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
jsonwebtoken = "9"
|
||||||
|
argon2 = { version = "0.5", features = ["password-hash"] }
|
||||||
|
futures = "0.3"
|
@ -24,5 +24,13 @@ pub fn init_db() -> Result<Connection> {
|
|||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS admin_users (
|
||||||
|
username TEXT PRIMARY KEY,
|
||||||
|
password_hash TEXT NOT NULL
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
|
use crate::models::{AdminUser, Claims, Form, LoginCredentials, Submission};
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use std::sync::{Arc, Mutex};
|
use serde_json::json;
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::models::{Form, Submission};
|
|
||||||
|
|
||||||
// Create a new form
|
// Create a new form
|
||||||
pub async fn create_form(
|
pub async fn create_form(
|
||||||
db: web::Data<Arc<Mutex<Connection>>>,
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
@ -49,13 +57,14 @@ pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>) -> impl Responder
|
|||||||
|
|
||||||
// Submit a form
|
// Submit a form
|
||||||
pub async fn submit_form(
|
pub async fn submit_form(
|
||||||
db: web::Data<Arc<Mutex<Connection>>>,
|
db: web::Data<Arc<Mutex<rusqlite::Connection>>>,
|
||||||
path: web::Path<String>,
|
path: web::Path<String>,
|
||||||
submission: web::Json<serde_json::Value>,
|
submission: web::Form<serde_json::Value>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
||||||
let submission_id = Uuid::new_v4().to_string();
|
let submission_id = Uuid::new_v4().to_string();
|
||||||
let form_id = path.into_inner();
|
let form_id = path.into_inner();
|
||||||
|
|
||||||
let submission_json = serde_json::to_string(&submission.into_inner()).unwrap();
|
let submission_json = serde_json::to_string(&submission.into_inner()).unwrap();
|
||||||
|
|
||||||
match conn.execute(
|
match conn.execute(
|
||||||
@ -94,3 +103,89 @@ pub async fn get_submissions(
|
|||||||
let submissions: Vec<Submission> = submissions_iter.filter_map(|s| s.ok()).collect();
|
let submissions: Vec<Submission> = submissions_iter.filter_map(|s| s.ok()).collect();
|
||||||
HttpResponse::Ok().json(submissions)
|
HttpResponse::Ok().json(submissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn admin_login(
|
||||||
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
|
credentials: web::Json<LoginCredentials>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let conn = db.lock().unwrap();
|
||||||
|
|
||||||
|
let mut stmt =
|
||||||
|
match conn.prepare("SELECT username, password_hash FROM admin_users WHERE username = ?1") {
|
||||||
|
Ok(stmt) => stmt,
|
||||||
|
Err(e) => return HttpResponse::InternalServerError().body(format!("Error: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let admin: Option<AdminUser> = stmt
|
||||||
|
.query_row([&credentials.username], |row| {
|
||||||
|
Ok(AdminUser {
|
||||||
|
username: row.get(0)?,
|
||||||
|
password_hash: row.get(1)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
match admin {
|
||||||
|
Some(user) => {
|
||||||
|
let parsed_hash = PasswordHash::new(&user.password_hash).unwrap();
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let is_valid = argon2
|
||||||
|
.verify_password(credentials.password.as_bytes(), &parsed_hash)
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
let expiration = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs() as usize
|
||||||
|
+ 24 * 3600;
|
||||||
|
|
||||||
|
let claims = Claims {
|
||||||
|
sub: user.username,
|
||||||
|
exp: expiration,
|
||||||
|
};
|
||||||
|
|
||||||
|
let token = encode(
|
||||||
|
&Header::default(),
|
||||||
|
&claims,
|
||||||
|
&EncodingKey::from_secret("your-secret-key".as_ref()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(json!({ "token": token }))
|
||||||
|
} else {
|
||||||
|
HttpResponse::Unauthorized().json(json!({
|
||||||
|
"error": "Invalid credentials"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => HttpResponse::Unauthorized().json(json!({
|
||||||
|
"error": "Invalid credentials"
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_admin(
|
||||||
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
|
user: web::Json<LoginCredentials>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let conn = db.lock().unwrap();
|
||||||
|
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let password_hash = argon2
|
||||||
|
.hash_password(user.password.as_bytes(), &salt)
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
match conn.execute(
|
||||||
|
"INSERT INTO admin_users (username, password_hash) VALUES (?1, ?2)",
|
||||||
|
params![user.username, password_hash],
|
||||||
|
) {
|
||||||
|
Ok(_) => HttpResponse::Ok().json(json!({
|
||||||
|
"message": "Admin user created successfully"
|
||||||
|
})),
|
||||||
|
Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,22 +1,39 @@
|
|||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
// use actix_files as fs;
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
mod handlers;
|
mod handlers; // Ensure handlers.rs exists
|
||||||
|
mod middleware; // Ensure middleware.rs exists // Ensure db.rs exists
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
|
use crate::middleware::AuthMiddleware;
|
||||||
|
use handlers::{admin_login, create_admin, create_form, get_forms, get_submissions, submit_form};
|
||||||
|
|
||||||
|
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(
|
||||||
|
web::scope("")
|
||||||
|
.route("/forms/{id}", web::post().to(submit_form))
|
||||||
|
.route("/admin/login", web::post().to(admin_login))
|
||||||
|
.route("/admin/create", web::post().to(create_admin)),
|
||||||
|
);
|
||||||
|
|
||||||
|
cfg.service(
|
||||||
|
web::scope("")
|
||||||
|
.wrap(AuthMiddleware)
|
||||||
|
.route("/forms", web::get().to(get_forms))
|
||||||
|
.route("/forms", web::post().to(create_form))
|
||||||
|
.route("/forms/{id}/submissions", web::get().to(get_submissions)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
// Initialize the database connection
|
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let db = Arc::new(Mutex::new(
|
let db = Arc::new(Mutex::new(
|
||||||
db::init_db().expect("Failed to initialize the database"),
|
db::init_db().expect("Failed to initialize the database"),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Start the Actix-Web server
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(
|
.wrap(
|
||||||
@ -26,17 +43,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.allow_any_method(),
|
.allow_any_method(),
|
||||||
)
|
)
|
||||||
.app_data(web::Data::new(db.clone()))
|
.app_data(web::Data::new(db.clone()))
|
||||||
// .service(fs::Files::new("/", "./frontend/public").index_file("index.html"))
|
.configure(configure_routes)
|
||||||
.route("/forms", web::post().to(handlers::create_form))
|
|
||||||
.route("/forms", web::get().to(handlers::get_forms))
|
|
||||||
.route(
|
|
||||||
"/forms/{id}/submissions",
|
|
||||||
web::post().to(handlers::submit_form),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/forms/{id}/submissions",
|
|
||||||
web::get().to(handlers::get_submissions),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.bind("127.0.0.1:8080")?
|
.bind("127.0.0.1:8080")?
|
||||||
.run()
|
.run()
|
||||||
|
87
backend/src/middleware.rs
Normal file
87
backend/src/middleware.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use crate::models::Claims;
|
||||||
|
use actix_web::body::{BoxBody, MessageBody};
|
||||||
|
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
|
||||||
|
use actix_web::{Error, HttpResponse};
|
||||||
|
use futures::future::{ok, Ready};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub struct AuthMiddleware;
|
||||||
|
|
||||||
|
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<BoxBody>; // Changed to BoxBody
|
||||||
|
type Error = Error;
|
||||||
|
type Transform = AuthMiddlewareService<S>;
|
||||||
|
type InitError = ();
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(AuthMiddlewareService { service })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthMiddlewareService<S> {
|
||||||
|
service: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<ServiceRequest> for AuthMiddlewareService<S>
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<BoxBody>; // Changed to BoxBody
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
if req.path() == "/admin/login" || req.path() == "/admin/create" {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
return Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
Ok(res.map_into_boxed_body()) // Convert the response body to BoxBody
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth_header = req.headers().get("Authorization");
|
||||||
|
match auth_header {
|
||||||
|
Some(header) => {
|
||||||
|
let token = header.to_str().unwrap_or("").replace("Bearer ", "");
|
||||||
|
if verify_token(&token) {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
Ok(res.map_into_boxed_body()) // Convert the response body to BoxBody
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let (request, _) = req.into_parts();
|
||||||
|
let response = HttpResponse::Unauthorized()
|
||||||
|
.json(json!({"error": "Invalid token"}))
|
||||||
|
.map_into_boxed_body();
|
||||||
|
Box::pin(async move { Ok(ServiceResponse::new(request, response)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let (request, _) = req.into_parts();
|
||||||
|
let response = HttpResponse::Unauthorized()
|
||||||
|
.json(json!({"error": "No authorization token"}))
|
||||||
|
.map_into_boxed_body();
|
||||||
|
Box::pin(async move { Ok(ServiceResponse::new(request, response)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_token(token: &str) -> bool {
|
||||||
|
let validation = jsonwebtoken::Validation::default();
|
||||||
|
let key = jsonwebtoken::DecodingKey::from_secret("your-secret-key".as_ref());
|
||||||
|
jsonwebtoken::decode::<Claims>(token, &key, &validation).is_ok()
|
||||||
|
}
|
@ -13,3 +13,21 @@ pub struct Submission {
|
|||||||
pub form_id: String,
|
pub form_id: String,
|
||||||
pub data: serde_json::Value, // JSON of submission data
|
pub data: serde_json::Value, // JSON of submission data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct AdminUser {
|
||||||
|
pub username: String,
|
||||||
|
pub password_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct LoginCredentials {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub sub: String,
|
||||||
|
pub(crate) exp: usize,
|
||||||
|
}
|
||||||
|
0
frontend/src/app.css
Normal file
0
frontend/src/app.css
Normal file
@ -1,4 +1,5 @@
|
|||||||
// api.ts
|
import type { Form, Submission, LoginCredentials } from './types';
|
||||||
|
|
||||||
const API_BASE_URL = 'http://127.0.0.1:8080';
|
const API_BASE_URL = 'http://127.0.0.1:8080';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +28,7 @@ export async function createForm(name: string, fields: unknown): Promise<string>
|
|||||||
* Get all forms.
|
* Get all forms.
|
||||||
* @returns An array of forms.
|
* @returns An array of forms.
|
||||||
*/
|
*/
|
||||||
export async function getForms(): Promise<unknown[]> {
|
export async function getForms(): Promise<Form[]> {
|
||||||
const response = await fetch(`${API_BASE_URL}/forms`, {
|
const response = await fetch(`${API_BASE_URL}/forms`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -69,7 +70,7 @@ export async function submitForm(formId: string, data: unknown): Promise<string>
|
|||||||
* @param formId The ID of the form.
|
* @param formId The ID of the form.
|
||||||
* @returns An array of submissions for the form.
|
* @returns An array of submissions for the form.
|
||||||
*/
|
*/
|
||||||
export async function getSubmissions(formId: string): Promise<unknown[]> {
|
export async function getSubmissions(formId: string): Promise<Submission[]> {
|
||||||
const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, {
|
const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -83,3 +84,47 @@ export async function getSubmissions(formId: string): Promise<unknown[]> {
|
|||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin login to get a token.
|
||||||
|
* @param credentials The login credentials (username and password).
|
||||||
|
* @returns The generated JWT token if successful.
|
||||||
|
*/
|
||||||
|
export async function adminLogin(credentials: LoginCredentials): Promise<string> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/admin/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(credentials)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error during admin login: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.token; // Assuming the response contains the token
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new admin user.
|
||||||
|
* @param user The login credentials for the admin user.
|
||||||
|
* @returns A success message upon creation.
|
||||||
|
*/
|
||||||
|
export async function createAdmin(user: LoginCredentials): Promise<string> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/admin/create`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(user)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error creating admin user: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.message; // Assuming the response contains a success message
|
||||||
|
}
|
||||||
|
28
frontend/src/lib/session.svelte.ts
Normal file
28
frontend/src/lib/session.svelte.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { AdminUser } from './types';
|
||||||
|
|
||||||
|
const key = 'user';
|
||||||
|
const key2 = 'username';
|
||||||
|
|
||||||
|
function login(user: AdminUser) {
|
||||||
|
localStorage.setItem(key, btoa(`${user.username}:${user.password_hash}`));
|
||||||
|
localStorage.setItem(key2, user.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
localStorage.removeItem(key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loggedIn() {
|
||||||
|
return localStorage.getItem(key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function name() {
|
||||||
|
return localStorage.getItem(key2) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth() {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { login, logout, loggedIn, name, auth };
|
@ -17,3 +17,13 @@ export interface Submission {
|
|||||||
data: Record<string, unknown>;
|
data: Record<string, unknown>;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LoginCredentials {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUser {
|
||||||
|
username: string;
|
||||||
|
password_hash: string;
|
||||||
|
}
|
||||||
|
8
frontend/src/routes/(auth)/+layout.ts
Normal file
8
frontend/src/routes/(auth)/+layout.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import session from "$lib/session.svelte";
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
if (!session.loggedIn()) {
|
||||||
|
redirect(307, "/login");
|
||||||
|
}
|
||||||
|
}
|
149
frontend/src/routes/(auth)/create/+page.svelte
Normal file
149
frontend/src/routes/(auth)/create/+page.svelte
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createForm } from '../../../lib/api';
|
||||||
|
import type { FormField } from '../../../lib/types';
|
||||||
|
|
||||||
|
let name = '';
|
||||||
|
let fields: FormField[] = [];
|
||||||
|
|
||||||
|
function addField() {
|
||||||
|
fields = [...fields, { label: '', name: '', field_type: 'text' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeField(index: number) {
|
||||||
|
fields = fields.filter((_, i) => i !== index);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveForm() {
|
||||||
|
try {
|
||||||
|
await createForm(name, fields);
|
||||||
|
alert('Form created successfully!');
|
||||||
|
location.href = '/';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create form:', error);
|
||||||
|
alert('An error occurred while creating the form.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Create Form</h1>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="form-name">Form Name</label>
|
||||||
|
<input id="form-name" type="text" bind:value={name} placeholder="Enter form name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Fields</h2>
|
||||||
|
{#each fields as field, i}
|
||||||
|
<div class="field-container">
|
||||||
|
<label>
|
||||||
|
Field Label
|
||||||
|
<input type="text" bind:value={field.label} placeholder="Enter field label" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Field Type
|
||||||
|
<select bind:value={field.field_type}>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
<option value="textarea">Textarea</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button class="button remove-button" on:click={() => removeField(i)}> Remove </button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button" on:click={addField}> Add Field </button>
|
||||||
|
<button class="button" on:click={saveForm} disabled={!name || fields.length === 0}>
|
||||||
|
Save Form
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-container {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover:not(:disabled) {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
background-color: #dc3545;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
179
frontend/src/routes/(auth)/form/[id]/+page.svelte
Normal file
179
frontend/src/routes/(auth)/form/[id]/+page.svelte
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { getForms, getSubmissions, submitForm } from '../../../lib/api';
|
||||||
|
import type { Form, Submission } from '../../../lib/types';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
export let params: { id: string };
|
||||||
|
|
||||||
|
let form: Form | null = null;
|
||||||
|
let submissions: Submission[] = [];
|
||||||
|
let responseData: Record<string, any> = {};
|
||||||
|
let isSubmitting = false;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { id } = $page.params;
|
||||||
|
if (id) {
|
||||||
|
form = await getForms().then((forms) => forms.find((f: Form) => f.id === id) || null);
|
||||||
|
submissions = await getSubmissions(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function submitResponse() {
|
||||||
|
if (isSubmitting) return;
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
try {
|
||||||
|
const { id } = $page.params;
|
||||||
|
await submitForm(id, responseData);
|
||||||
|
submissions = await getSubmissions(params.id);
|
||||||
|
responseData = {};
|
||||||
|
alert('Response submitted successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to submit response. Please try again.');
|
||||||
|
} finally {
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSubmissionData(data: Record<string, any>): string {
|
||||||
|
return Object.entries(data)
|
||||||
|
.map(([key, value]) => `${key}: ${value}`)
|
||||||
|
.join(' | ');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if form}
|
||||||
|
<h1>{form.name}</h1>
|
||||||
|
<!-- <div class="form-container">
|
||||||
|
<form on:submit|preventDefault={submitResponse}>
|
||||||
|
{#each form.fields as field}
|
||||||
|
<div class="field-group">
|
||||||
|
<label for={field.name}>{field.label}</label>
|
||||||
|
{#if field.field_type === 'textarea'}
|
||||||
|
<textarea id={field.name} bind:value={responseData[field.name]}></textarea>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
type={field.field_type}
|
||||||
|
bind:value={responseData[field.name]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<button type="submit" class="submit-button" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? 'Submitting...' : 'Submit Response'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<h2>Submissions</h2>
|
||||||
|
{#if submissions.length > 0}
|
||||||
|
<ul class="submissions-list">
|
||||||
|
{#each submissions as submission}
|
||||||
|
<li class="submission-item">
|
||||||
|
{formatSubmissionData(submission.data)}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{:else}
|
||||||
|
<p>No submissions yet.</p>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="loading">Loading...</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:hover:not(:disabled) {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submissions-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submission-item {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 4px solid #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
6
frontend/src/routes/+layout.svelte
Normal file
6
frontend/src/routes/+layout.svelte
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
import '../app.css';
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children?.()}
|
1
frontend/src/routes/+layout.ts
Normal file
1
frontend/src/routes/+layout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const ssr = false;
|
@ -3,21 +3,88 @@
|
|||||||
import { getForms } from '../lib/api';
|
import { getForms } from '../lib/api';
|
||||||
import type { Form } from '../lib/types';
|
import type { Form } from '../lib/types';
|
||||||
|
|
||||||
let forms: any;
|
let forms: Form[] = [];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
forms = await getForms();
|
forms = await getForms();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>Form Management Tool</h1>
|
<div class="container">
|
||||||
|
<h1>Formies</h1>
|
||||||
|
<a href="/create" class="create-button">Create a New Form</a>
|
||||||
|
|
||||||
<a href="/create">Create a New Form</a>
|
{#if forms.length > 0}
|
||||||
|
<ul class="forms-list">
|
||||||
|
{#each forms as form}
|
||||||
|
<li class="form-item">
|
||||||
|
<a href={`/form/${form.id}`} class="form-link">
|
||||||
|
{form.name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{:else}
|
||||||
|
<div class="empty-state">
|
||||||
|
<p>No forms created yet. Create your first form to get started!</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<style>
|
||||||
{#each forms as form}
|
.container {
|
||||||
<li>
|
max-width: 800px;
|
||||||
<a href={`/form/${form.id}`}>{form.name}</a>
|
margin: 0 auto;
|
||||||
</li>
|
padding: 2rem;
|
||||||
{/each}
|
}
|
||||||
</ul>
|
|
||||||
|
h1 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #42b983;
|
||||||
|
color: white;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-button:hover {
|
||||||
|
background-color: #3aa876;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forms-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item:hover {
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-link {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
text-decoration: none;
|
||||||
|
border-left: 4px solid #42b983;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
7
frontend/src/routes/+page.ts
Normal file
7
frontend/src/routes/+page.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import session from '$lib/session.svelte';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
const page = session.loggedIn() ? '/' : '/login';
|
||||||
|
redirect(307, page);
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createForm } from '../../lib/api';
|
|
||||||
import type { FormField } from '../../lib/types';
|
|
||||||
|
|
||||||
let name = '';
|
|
||||||
let fields: FormField[] = [];
|
|
||||||
|
|
||||||
function addField() {
|
|
||||||
// Use a new array assignment to trigger reactivity
|
|
||||||
fields = [...fields, { label: '', name: '', field_type: 'text' }];
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeField(index: number) {
|
|
||||||
// Reassign to trigger reactivity
|
|
||||||
fields = fields.filter((_, i) => i !== index);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveForm() {
|
|
||||||
try {
|
|
||||||
await createForm(name, fields);
|
|
||||||
alert('Form created successfully!');
|
|
||||||
location.href = '/';
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create form:', error);
|
|
||||||
alert('An error occurred while creating the form.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Create Form</h1>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Form Name:
|
|
||||||
<input type="text" bind:value={name} placeholder="Enter form name" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<h2>Fields</h2>
|
|
||||||
{#each fields as field, i}
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
Label:
|
|
||||||
<input type="text" bind:value={field.label} placeholder="Enter field label" />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Name:
|
|
||||||
<input type="text" bind:value={field.name} placeholder="Enter field name" />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Type:
|
|
||||||
<select bind:value={field.field_type}>
|
|
||||||
<option value="text">Text</option>
|
|
||||||
<option value="number">Number</option>
|
|
||||||
<option value="date">Date</option>
|
|
||||||
<option value="textarea">Textarea</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<button on:click={() => removeField(i)}>Remove</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<button on:click={addField}>Add Field</button>
|
|
||||||
<button on:click={saveForm} disabled={!name || fields.length === 0}> Save Form </button>
|
|
@ -1,61 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { getForms, getSubmissions, submitForm } from '../../../lib/api';
|
|
||||||
import type { Form, Submission } from '../../../lib/types';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
export let params: { id: string };
|
|
||||||
console.log('params.id:', params);
|
|
||||||
let form: any | null = null;
|
|
||||||
let submissions: any[] = [];
|
|
||||||
let responseData: Record<string, any> = {};
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const { id } = $page.params; // Use $page.params to access route parameters
|
|
||||||
if (id) {
|
|
||||||
form = await getForms().then((forms) => forms.find((f: any) => f.id === id) || null);
|
|
||||||
submissions = await getSubmissions(id);
|
|
||||||
} else {
|
|
||||||
console.error('Route parameter id is missing');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function submitResponse() {
|
|
||||||
const { id } = $page.params; // Use $page.params to access route parameters
|
|
||||||
await submitForm(id, responseData);
|
|
||||||
alert('Response submitted successfully!');
|
|
||||||
submissions = await getSubmissions(params.id); // Refresh submissions
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>{form?.name}</h1>
|
|
||||||
|
|
||||||
{#if form}
|
|
||||||
<form on:submit|preventDefault={submitResponse}>
|
|
||||||
{#each form.fields as field}
|
|
||||||
<div>
|
|
||||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
||||||
<label>{field.label}</label>
|
|
||||||
{#if field.field_type === 'text'}
|
|
||||||
<input type="text" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'number'}
|
|
||||||
<input type="number" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'date'}
|
|
||||||
<input type="date" bind:value={responseData[field.name]} />
|
|
||||||
{:else if field.field_type === 'textarea'}
|
|
||||||
<textarea bind:value={responseData[field.name]}></textarea>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2>Submissions</h2>
|
|
||||||
<ul>
|
|
||||||
{#each submissions as submission}
|
|
||||||
<li>{JSON.stringify(submission.data)}</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:else}
|
|
||||||
<p>Loading...</p>
|
|
||||||
{/if}
|
|
105
frontend/src/routes/login/+page.svelte
Normal file
105
frontend/src/routes/login/+page.svelte
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { adminLogin } from '$lib/api';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
let username = '';
|
||||||
|
let password = '';
|
||||||
|
let errorMessage = '';
|
||||||
|
let loading = false;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
async function handleSubmit(event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
errorMessage = '';
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await adminLogin({ username, password });
|
||||||
|
dispatch('login', { token }); // Dispatch the token to the parent component or handle it here
|
||||||
|
alert('Login successful!');
|
||||||
|
} catch (error: any) {
|
||||||
|
errorMessage = error.message || 'Login failed. Please try again.';
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit={handleSubmit} class="login-form">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
class="input-field"
|
||||||
|
bind:value={username}
|
||||||
|
placeholder="Enter your username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
class="input-field"
|
||||||
|
bind:value={password}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button type="submit" class="submit-btn" disabled={loading}>
|
||||||
|
{loading ? 'Logging in...' : 'Login'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if errorMessage}
|
||||||
|
<div class="error-message">{errorMessage}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="loading">Loading...</div>
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
8
frontend/src/routes/login/+page.ts
Normal file
8
frontend/src/routes/login/+page.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import session from '$lib/session.svelte';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
if (session.loggedIn()) {
|
||||||
|
redirect(307, '/');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user