添加登录页面
This commit is contained in:
parent
4b052d8b2a
commit
16b3b63169
@ -10,15 +10,13 @@
|
|||||||
<cargoProject FILE="$PROJECT_DIR$/bookkeeper/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/bookkeeper/Cargo.toml" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="清仓筛选">
|
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="添加总计">
|
||||||
|
<change afterPath="$PROJECT_DIR$/bookkeeper/templates/login.html.j2" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.lock" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.lock" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.toml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/migration/src/m20220101_000001_create_table.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/src/m20220101_000001_create_table.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/bookkeeper/Rocket.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Rocket.toml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/src/main.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/bookkeeper/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/src/main.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/tx.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/tx.html.j2" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -30,6 +28,7 @@
|
|||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
<list>
|
<list>
|
||||||
<option value="HTML File" />
|
<option value="HTML File" />
|
||||||
|
<option value="Rust File" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
@ -158,7 +157,8 @@
|
|||||||
<workItem from="1736238929025" duration="646000" />
|
<workItem from="1736238929025" duration="646000" />
|
||||||
<workItem from="1736239592084" duration="2278000" />
|
<workItem from="1736239592084" duration="2278000" />
|
||||||
<workItem from="1737809478583" duration="76000" />
|
<workItem from="1737809478583" duration="76000" />
|
||||||
<workItem from="1739197188908" duration="1079000" />
|
<workItem from="1739197188908" duration="1873000" />
|
||||||
|
<workItem from="1740209396270" duration="4798000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Add bookkeeper">
|
<task id="LOCAL-00001" summary="Add bookkeeper">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@ -264,7 +264,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1736241771673</updated>
|
<updated>1736241771673</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="14" />
|
<task id="LOCAL-00014" summary="添加总计">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1739198426101</created>
|
||||||
|
<option name="number" value="00014" />
|
||||||
|
<option name="presentableId" value="LOCAL-00014" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1739198426101</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="15" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@ -295,7 +303,8 @@
|
|||||||
<MESSAGE value="Filter by name" />
|
<MESSAGE value="Filter by name" />
|
||||||
<MESSAGE value="更新版本号和部署脚本" />
|
<MESSAGE value="更新版本号和部署脚本" />
|
||||||
<MESSAGE value="清仓筛选" />
|
<MESSAGE value="清仓筛选" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="清仓筛选" />
|
<MESSAGE value="添加总计" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="添加总计" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
178
bookkeeper/Cargo.lock
generated
178
bookkeeper/Cargo.lock
generated
@ -17,13 +17,48 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes-gcm"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"aes",
|
||||||
|
"cipher",
|
||||||
|
"ctr",
|
||||||
|
"ghash",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@ -352,6 +387,19 @@ version = "1.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"blowfish",
|
||||||
|
"getrandom 0.3.1",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bigdecimal"
|
name = "bigdecimal"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
@ -422,9 +470,20 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bookkeeper"
|
name = "blowfish"
|
||||||
version = "0.1.3"
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bookkeeper"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
"entity",
|
"entity",
|
||||||
"migration",
|
"migration",
|
||||||
@ -574,6 +633,16 @@ dependencies = [
|
|||||||
"phf_codegen",
|
"phf_codegen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.23"
|
version = "4.5.23"
|
||||||
@ -641,7 +710,13 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes-gcm",
|
||||||
|
"base64",
|
||||||
|
"hkdf",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"rand",
|
||||||
|
"sha2",
|
||||||
|
"subtle",
|
||||||
"time",
|
"time",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@ -726,9 +801,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctr"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.10"
|
version = "0.20.10"
|
||||||
@ -1169,7 +1254,29 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||||
|
dependencies = [
|
||||||
|
"opaque-debug",
|
||||||
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1640,6 +1747,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@ -1884,7 +2000,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1895,7 +2011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2054,6 +2170,12 @@ version = "1.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "3.9.2"
|
version = "3.9.2"
|
||||||
@ -2324,6 +2446,18 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polyval"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -2467,7 +2601,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2560,7 +2694,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"libc",
|
"libc",
|
||||||
"spin",
|
"spin",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
@ -3934,6 +4068,16 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -4033,6 +4177,15 @@ 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 = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasite"
|
name = "wasite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -4341,6 +4494,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bookkeeper"
|
name = "bookkeeper"
|
||||||
version = "0.1.3"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.39"
|
||||||
rocket = "0.5.1"
|
rocket = { version = "0.5.1", features = ["secrets"] }
|
||||||
rocket_dyn_templates = { version = "0.2.0", features = ["tera", "minijinja"]}
|
rocket_dyn_templates = { version = "0.2.0", features = ["tera", "minijinja"]}
|
||||||
migration = { path = "migration" }
|
migration = { path = "migration" }
|
||||||
entity ={ path = "entity"}
|
entity ={ path = "entity"}
|
||||||
@ -13,6 +13,7 @@ rust_decimal = "1.36.0"
|
|||||||
sea-orm-rocket = "0.5.4"
|
sea-orm-rocket = "0.5.4"
|
||||||
serde_json = "1.0.134"
|
serde_json = "1.0.134"
|
||||||
serde = { version = "1.0.216", features = ["derive"] }
|
serde = { version = "1.0.216", features = ["derive"] }
|
||||||
|
bcrypt = "0.17.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "entity", "migration"]
|
members = [".", "entity", "migration"]
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
[default]
|
[default]
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0"
|
||||||
limits = { form = "64 kB", json = "1 MiB" }
|
limits = { form = "64 kB", json = "1 MiB" }
|
||||||
|
secret_key = "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
|
||||||
|
|
||||||
[default.databases.bookkeeper]
|
[default.databases.bookkeeper]
|
||||||
url = "sqlite://bookkeeper.db"
|
url = "sqlite://bookkeeper.db"
|
||||||
@ -20,7 +21,6 @@ port = 9001
|
|||||||
[release]
|
[release]
|
||||||
port = 9999
|
port = 9999
|
||||||
ip_header = false
|
ip_header = false
|
||||||
secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="
|
|
||||||
|
|
||||||
[global.templates]
|
[global.templates]
|
||||||
dir = "templates"
|
dir = "templates"
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
|
use bcrypt::{hash_with_salt, DEFAULT_COST};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
@ -10,8 +11,10 @@ use migration::MigratorTrait;
|
|||||||
use rocket::fairing::{self, AdHoc};
|
use rocket::fairing::{self, AdHoc};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::fs::FileServer;
|
use rocket::fs::FileServer;
|
||||||
use rocket::http::Status;
|
use rocket::http::{Cookie, CookieJar, Status};
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::State;
|
||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
use rocket_dyn_templates::{context, Engines, Template};
|
use rocket_dyn_templates::{context, Engines, Template};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@ -20,8 +23,121 @@ use sea_orm::ActiveValue::Set;
|
|||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectOptions, DatabaseConnection, EntityTrait};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectOptions, DatabaseConnection, EntityTrait};
|
||||||
use sea_orm_rocket::{rocket::figment::Figment, Config, Connection, Database};
|
use sea_orm_rocket::{rocket::figment::Figment, Config, Connection, Database};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::sync::Mutex;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
// static CORRECT_PASSWORD: &str = "Admin!23-ca$hc0w";
|
||||||
|
static CORRECT_HASH: &str = "$2y$12$Q1TvUkuzKBThOlfOUjOuKexKJCDtVvz2RznuUbQntD1TTYLo4PWwy";
|
||||||
|
|
||||||
|
/// 记录单个 IP 的错误登录记录和封禁截止时间
|
||||||
|
#[derive(Default)]
|
||||||
|
struct LoginAttempt {
|
||||||
|
/// 记录每次错误的时间(只保留5分钟内的记录)
|
||||||
|
failed_times: Vec<SystemTime>,
|
||||||
|
/// 封禁截止时间,如果未封禁则为 None
|
||||||
|
banned_until: Option<SystemTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 全局状态,记录每个 IP 的登录尝试情况
|
||||||
|
struct LoginState {
|
||||||
|
attempts: Mutex<HashMap<String, LoginAttempt>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginState {
|
||||||
|
fn new() -> Self {
|
||||||
|
LoginState {
|
||||||
|
attempts: Mutex::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录表单数据
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct LoginForm {
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 自定义请求守卫,用于提取客户端 IP 地址
|
||||||
|
struct ClientIP(String);
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for ClientIP {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
if let Some(ip) = req.client_ip() {
|
||||||
|
Outcome::Success(ClientIP(ip.to_string()))
|
||||||
|
} else {
|
||||||
|
Outcome::Error((Status::BadRequest, ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /login 显示登录页面
|
||||||
|
#[get("/login")]
|
||||||
|
fn login_page() -> Template {
|
||||||
|
let context = HashMap::<String, String>::new();
|
||||||
|
Template::render("login", &context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /login 处理登录请求
|
||||||
|
#[post("/login", data = "<login_form>")]
|
||||||
|
fn login_post(
|
||||||
|
login_form: Form<LoginForm>,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
state: &State<LoginState>,
|
||||||
|
client_ip: ClientIP,
|
||||||
|
) -> Result<Redirect, Template> {
|
||||||
|
let ip = client_ip.0;
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let mut attempts = state.attempts.lock().unwrap();
|
||||||
|
let entry = attempts
|
||||||
|
.entry(ip.clone())
|
||||||
|
.or_insert(LoginAttempt::default());
|
||||||
|
|
||||||
|
// 检查该 IP 是否处于封禁状态
|
||||||
|
if let Some(banned_until) = entry.banned_until {
|
||||||
|
if now < banned_until {
|
||||||
|
let mut context = HashMap::new();
|
||||||
|
context.insert(
|
||||||
|
"error".to_string(),
|
||||||
|
"您的IP已被禁止访问,请稍后再试。".to_string(),
|
||||||
|
);
|
||||||
|
return Err(Template::render("login", &context));
|
||||||
|
} else {
|
||||||
|
// 封禁时间已过,清除记录
|
||||||
|
entry.banned_until = None;
|
||||||
|
entry.failed_times.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅保留过去5分钟内的错误记录
|
||||||
|
entry.failed_times.retain(|&t| {
|
||||||
|
now.duration_since(t).unwrap_or(Duration::new(0, 0)) < Duration::from_secs(300)
|
||||||
|
});
|
||||||
|
|
||||||
|
let pass_hash = hash_with_salt(&login_form.password, DEFAULT_COST, *b"KuqZl505cBxPZT02")
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
// 使用 bcrypt 对输入的密码进行验证
|
||||||
|
if pass_hash == CORRECT_HASH {
|
||||||
|
// 密码正确:清除错误记录,设置登录 Cookie
|
||||||
|
entry.failed_times.clear();
|
||||||
|
cookies.add_private(Cookie::new("auth", pass_hash));
|
||||||
|
Ok(Redirect::to("/"))
|
||||||
|
} else {
|
||||||
|
// 密码错误:记录错误时间,判断是否需要封禁IP
|
||||||
|
entry.failed_times.push(now);
|
||||||
|
if entry.failed_times.len() > 5 {
|
||||||
|
entry.banned_until = Some(now + Duration::from_secs(7200));
|
||||||
|
}
|
||||||
|
let mut context = HashMap::new();
|
||||||
|
context.insert("error".to_string(), "密码错误!".to_string());
|
||||||
|
Err(Template::render("login", &context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(sea_orm_rocket::Database, Debug)]
|
#[derive(sea_orm_rocket::Database, Debug)]
|
||||||
#[database("bookkeeper")]
|
#[database("bookkeeper")]
|
||||||
@ -57,13 +173,28 @@ impl sea_orm_rocket::Pool for SeaOrmPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify(cookies: &CookieJar<'_>) -> bool {
|
||||||
|
if let Some(c) = cookies.get_private("auth") {
|
||||||
|
// println!("{} ? {}", c.value_trimmed(), )
|
||||||
|
if c.value_trimmed() == CORRECT_HASH {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/?<last>&<name>&<is_done>")]
|
#[get("/?<last>&<name>&<is_done>")]
|
||||||
async fn index(
|
async fn index(
|
||||||
conn: Connection<'_, Db>,
|
conn: Connection<'_, Db>,
|
||||||
last: Option<u32>,
|
last: Option<u32>,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
is_done: Option<bool>,
|
is_done: Option<bool>,
|
||||||
) -> Template {
|
cookies: &CookieJar<'_>,
|
||||||
|
) -> Result<Template, Redirect> {
|
||||||
|
if !verify(cookies) {
|
||||||
|
return Err(Redirect::to("/login"));
|
||||||
|
}
|
||||||
|
|
||||||
let mut partial = TE::find();
|
let mut partial = TE::find();
|
||||||
|
|
||||||
if let Some(last) = last {
|
if let Some(last) = last {
|
||||||
@ -103,11 +234,11 @@ async fn index(
|
|||||||
net_gain += ng;
|
net_gain += ng;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pct = gain / bought;
|
let pct = gain / bought;
|
||||||
let net_pct = net_gain / net_bought;
|
let net_pct = net_gain / net_bought;
|
||||||
|
|
||||||
Template::render(
|
Ok(Template::render(
|
||||||
"index",
|
"index",
|
||||||
context! {
|
context! {
|
||||||
rows: rows,
|
rows: rows,
|
||||||
@ -117,7 +248,7 @@ async fn index(
|
|||||||
pct: pct,
|
pct: pct,
|
||||||
net_pct: net_pct,
|
net_pct: net_pct,
|
||||||
},
|
},
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm, Debug)]
|
#[derive(FromForm, Debug)]
|
||||||
@ -150,7 +281,14 @@ struct SplitPiece {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/tx/<id>")]
|
#[get("/tx/<id>")]
|
||||||
async fn get_tx(conn: Connection<'_, Db>, id: i32) -> Result<Template, Status> {
|
async fn get_tx(
|
||||||
|
conn: Connection<'_, Db>,
|
||||||
|
id: i32,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
) -> Result<Template, Status> {
|
||||||
|
if !verify(cookies) {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
}
|
||||||
let row = TE::find_by_id(id).one(conn.into_inner()).await.unwrap();
|
let row = TE::find_by_id(id).one(conn.into_inner()).await.unwrap();
|
||||||
match row {
|
match row {
|
||||||
None => Err(Status::NotFound),
|
None => Err(Status::NotFound),
|
||||||
@ -169,7 +307,11 @@ async fn post_tx(
|
|||||||
data: Form<TransModel<'_>>,
|
data: Form<TransModel<'_>>,
|
||||||
conn: Connection<'_, Db>,
|
conn: Connection<'_, Db>,
|
||||||
id: i32,
|
id: i32,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
) -> Result<Redirect, Status> {
|
) -> Result<Redirect, Status> {
|
||||||
|
if !verify(cookies) {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
}
|
||||||
let mut record = translate(data);
|
let mut record = translate(data);
|
||||||
record.id = Set(id);
|
record.id = Set(id);
|
||||||
match record.update(conn.into_inner()).await {
|
match record.update(conn.into_inner()).await {
|
||||||
@ -179,7 +321,14 @@ async fn post_tx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/tx/<id>")]
|
#[delete("/tx/<id>")]
|
||||||
async fn delete_tx(conn: Connection<'_, Db>, id: i32) -> Result<Redirect, Status> {
|
async fn delete_tx(
|
||||||
|
conn: Connection<'_, Db>,
|
||||||
|
id: i32,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
if !verify(cookies) {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
}
|
||||||
match TE::delete_by_id(id).exec(conn.into_inner()).await {
|
match TE::delete_by_id(id).exec(conn.into_inner()).await {
|
||||||
Err(_) => Err(Status::InternalServerError),
|
Err(_) => Err(Status::InternalServerError),
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
@ -193,23 +342,34 @@ async fn delete_tx(conn: Connection<'_, Db>, id: i32) -> Result<Redirect, Status
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/add")]
|
#[get("/add")]
|
||||||
async fn get_add() -> Template {
|
async fn get_add(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
||||||
Template::render(
|
if !verify(cookies) {
|
||||||
|
return Err(Redirect::to("/login"));
|
||||||
|
}
|
||||||
|
Ok(Template::render(
|
||||||
"tx",
|
"tx",
|
||||||
context! {
|
context! {
|
||||||
r: (),
|
r: (),
|
||||||
target: "/add",
|
target: "/add",
|
||||||
},
|
},
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/add", data = "<trans>")]
|
#[post("/add", data = "<trans>")]
|
||||||
async fn add(trans: Form<TransModel<'_>>, conn: Connection<'_, Db>) -> Flash<Redirect> {
|
async fn add(
|
||||||
|
trans: Form<TransModel<'_>>,
|
||||||
|
conn: Connection<'_, Db>,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
if !verify(cookies) {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
let record = translate(trans);
|
let record = translate(trans);
|
||||||
|
|
||||||
record.insert(conn.into_inner()).await.unwrap();
|
record.insert(conn.into_inner()).await.unwrap();
|
||||||
|
|
||||||
Flash::success(Redirect::to("/"), "OK!")
|
Ok(Redirect::to("/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// translate from form to active model of transaction
|
/// translate from form to active model of transaction
|
||||||
@ -268,6 +428,7 @@ async fn rocket() -> _ {
|
|||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(Db::init())
|
.attach(Db::init())
|
||||||
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
||||||
|
.manage(LoginState::new())
|
||||||
.attach(Template::custom(|engines: &mut Engines| {
|
.attach(Template::custom(|engines: &mut Engines| {
|
||||||
engines
|
engines
|
||||||
.minijinja
|
.minijinja
|
||||||
@ -281,7 +442,7 @@ async fn rocket() -> _ {
|
|||||||
}))
|
}))
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![index, add, get_add, get_tx, post_tx, delete_tx],
|
routes![index, add, get_add, get_tx, post_tx, delete_tx, login_page, login_post],
|
||||||
)
|
)
|
||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
}
|
}
|
||||||
|
|||||||
17
bookkeeper/templates/login.html.j2
Normal file
17
bookkeeper/templates/login.html.j2
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>登录</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>登录页面</h1>
|
||||||
|
{% if error %}
|
||||||
|
<p style="color: red;">{{ error }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<label>密码:<input type="password" name="password"></label>
|
||||||
|
<button type="submit">登录</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user