Compare commits
12 Commits
d9105957a8
...
6b73b64a3a
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b73b64a3a | |||
|
|
f4e6eb56c8 | ||
|
|
f71cf370cd | ||
|
|
fae951c902 | ||
|
|
8b98590a1e | ||
|
|
6cd55d06a2 | ||
|
|
547d73fab7 | ||
|
|
537737b1cc | ||
|
|
bb622df89b | ||
|
|
e849b71a40 | ||
|
|
56c1161e97 | ||
|
|
f9218e0927 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,4 +23,7 @@ target
|
||||
#.idea/
|
||||
|
||||
# generated environment variables file
|
||||
.env
|
||||
.env.generated
|
||||
|
||||
generated-config.yaml
|
||||
|
||||
453
Cargo.lock
generated
453
Cargo.lock
generated
@@ -93,6 +93,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@@ -153,6 +159,70 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
@@ -372,9 +442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.51"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -382,9 +452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.51"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -441,12 +511,52 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.15.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"convert_case",
|
||||
"json5",
|
||||
"pathdiff",
|
||||
"ron",
|
||||
"rust-ini",
|
||||
"serde-untagged",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"toml",
|
||||
"winnow",
|
||||
"yaml-rust2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "container-simulate"
|
||||
version = "0.1.0"
|
||||
@@ -454,12 +564,22 @@ dependencies = [
|
||||
"async-trait",
|
||||
"clap",
|
||||
"path-clean",
|
||||
"serde_json",
|
||||
"shared",
|
||||
"testcontainers",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -525,6 +645,12 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -609,6 +735,7 @@ name = "database"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
"migration",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
@@ -682,6 +809,15 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docker_credential"
|
||||
version = "1.3.2"
|
||||
@@ -714,12 +850,32 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -988,6 +1144,12 @@ dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -1400,6 +1562,17 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -1479,6 +1652,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
@@ -1499,11 +1678,18 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
name = "migration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"sea-orm-cli",
|
||||
"sea-orm-migration",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
@@ -1532,6 +1718,15 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -1659,6 +1854,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.18.5"
|
||||
@@ -1743,6 +1948,12 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@@ -1758,6 +1969,49 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pgvector"
|
||||
version = "0.4.1"
|
||||
@@ -2093,6 +2347,20 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"typeid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.9"
|
||||
@@ -2113,6 +2381,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.39.0"
|
||||
@@ -2484,6 +2762,18 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-untagged"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
|
||||
dependencies = [
|
||||
"erased-serde",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
@@ -2517,6 +2807,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -2528,6 +2829,15 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -2977,6 +3287,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
@@ -3096,6 +3412,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
@@ -3135,6 +3460,7 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -3198,6 +3524,19 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
@@ -3228,6 +3567,28 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
@@ -3264,6 +3625,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3272,13 +3655,21 @@ version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"time",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3287,12 +3678,24 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -3320,6 +3723,12 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
@@ -3367,6 +3776,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -3834,12 +4249,42 @@ dependencies = [
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust2"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9"
|
||||
dependencies = [
|
||||
"arraydeque",
|
||||
"encoding_rs",
|
||||
"hashlink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yet-another-nginx-proxy-manager"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"chrono",
|
||||
"config",
|
||||
"database",
|
||||
"migration",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"apps/api",
|
||||
"apps/container",
|
||||
"apps/cli",
|
||||
"public/shared",
|
||||
@@ -11,3 +12,8 @@ resolver = "3"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
module_inception = "allow"
|
||||
|
||||
[workspace.dependencies]
|
||||
sea-orm = "2.0.0-rc"
|
||||
sea-orm-cli = "2.0.0-rc"
|
||||
sea-orm-migration = "2.0.0-rc"
|
||||
|
||||
1
apps/api/.gitignore
vendored
Normal file
1
apps/api/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
config.yaml
|
||||
20
apps/api/Cargo.toml
Normal file
20
apps/api/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "yet-another-nginx-proxy-manager"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
database = { path = "../../public/database" }
|
||||
migration = { path = "../../public/migration" }
|
||||
|
||||
axum = { version = "0.8.7", features = ["form", "http1", "json", "matched-path", "original-uri", "query", "tokio", "tower-log", "tracing", "macros"]}
|
||||
async-trait = { version = "0.1.89" }
|
||||
chrono = { version = "0.4.42", features = ["clock", "std", "oldtime", "wasmbind", "serde"] }
|
||||
config = { version = "0.15.19", features = ["toml", "json", "yaml", "ini", "ron", "json5", "convert-case", "async"] }
|
||||
tokio = { version = "1", features = ["fs", "io-util", "io-std", "macros", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "sync", "time", "tracing"] }
|
||||
tower = { version = "0.5.2", features = ["tokio", "tracing", "timeout"] }
|
||||
tracing = { version = "0.1.41", features = ["std", "attributes"] }
|
||||
tracing-subscriber = { version = "0.3.20", features = ["smallvec", "fmt", "ansi", "tracing-log", "std", "chrono", "json", "serde", "serde_json", "time", "tracing"] }
|
||||
serde_json = { version = "1.0.145", features = ["std"] }
|
||||
serde = { version = "1.0.228", features = ["std", "derive"] }
|
||||
sea-orm = { workspace = true }
|
||||
76
apps/api/src/configs.rs
Normal file
76
apps/api/src/configs.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
pub mod database;
|
||||
pub mod logging;
|
||||
pub mod server;
|
||||
|
||||
mod key;
|
||||
|
||||
use config::Config;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub trait FromConfig: Sized {
|
||||
fn from_config(config: &Config) -> Result<Self, String>;
|
||||
fn validate(&self) -> Result<(), String>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProgramSettings {
|
||||
pub logging: logging::LoggingSettings,
|
||||
pub database: database::DatabaseSettings,
|
||||
pub server: server::ServerSettings,
|
||||
}
|
||||
|
||||
impl FromConfig for ProgramSettings {
|
||||
fn from_config(_config: &Config) -> Result<Self, String> {
|
||||
let config = ProgramSettings {
|
||||
logging: logging::LoggingSettings::from_config(_config)?,
|
||||
database: database::DatabaseSettings::from_config(_config)?,
|
||||
server: server::ServerSettings::from_config(_config)?,
|
||||
};
|
||||
config.validate()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
self.logging.validate()?;
|
||||
self.database.validate()?;
|
||||
self.server.validate()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_program_settings() -> ProgramSettings {
|
||||
debug!("Loading program settings from configuration sources");
|
||||
let settings = Config::builder()
|
||||
// dev / generated config has the highest priority (Overwrite by user config files)
|
||||
.add_source(config::File::with_name("generated-config.yaml").required(false))
|
||||
// user config files
|
||||
.add_source(
|
||||
config::File::with_name("/etc/yet-another-nginx-proxy-manager/config").required(false),
|
||||
)
|
||||
.add_source(
|
||||
config::File::with_name("$HOME/.config/yet-another-nginx-proxy-manager/config")
|
||||
.required(false),
|
||||
)
|
||||
.add_source(config::File::with_name("config.yaml").required(false))
|
||||
// environment variables have the highest priority (Overwrite all config files)
|
||||
.add_source(
|
||||
config::Environment::with_prefix("YANPM")
|
||||
.separator("__")
|
||||
.prefix_separator("_"),
|
||||
)
|
||||
.build()
|
||||
.expect("Failed to build configuration");
|
||||
|
||||
debug!("Configuration sources loaded successfully");
|
||||
debug!("Parsing program settings from configuration");
|
||||
|
||||
ProgramSettings::from_config(&settings)
|
||||
.inspect_err(|err| {
|
||||
error!("Configuration error: {}", err);
|
||||
debug!("Current configurations: {:#?}", settings);
|
||||
})
|
||||
.inspect(|_| {
|
||||
debug!("Program settings parsed successfully");
|
||||
})
|
||||
.expect("Failed to load program settings from configuration")
|
||||
}
|
||||
53
apps/api/src/configs/database.rs
Normal file
53
apps/api/src/configs/database.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use config::{Config, ConfigError};
|
||||
use tracing::warn;
|
||||
|
||||
use super::{
|
||||
FromConfig,
|
||||
key::{DATABASE_MAX_CONNECTIONS_KEY, DATABASE_MIGRATE_ON_STARTUP_KEY},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseSettings {
|
||||
pub url: String,
|
||||
pub max_connections: u32,
|
||||
pub migrate_on_startup: bool,
|
||||
}
|
||||
|
||||
impl FromConfig for DatabaseSettings {
|
||||
fn from_config(_config: &Config) -> Result<Self, String> {
|
||||
Ok(DatabaseSettings {
|
||||
url: _config
|
||||
.get_string(super::key::DATABASE_URL_KEY)
|
||||
.map_err(|op| match op {
|
||||
ConfigError::NotFound(_) => "Database URL not found in configuration".into(),
|
||||
err => {
|
||||
format!("Failed to read Database URL from configuration {err}")
|
||||
}
|
||||
})?,
|
||||
max_connections: _config
|
||||
.get_int(DATABASE_MAX_CONNECTIONS_KEY)
|
||||
.unwrap_or_else(|err| {
|
||||
const DEFAULT_MAX_CONNECTIONS: i64 = 10;
|
||||
warn!(
|
||||
"{} not set or invalid in configuration, defaulting to {}. Error: {}",
|
||||
DATABASE_MAX_CONNECTIONS_KEY, DEFAULT_MAX_CONNECTIONS, err
|
||||
);
|
||||
DEFAULT_MAX_CONNECTIONS
|
||||
}) as u32,
|
||||
migrate_on_startup: _config
|
||||
.get_bool(DATABASE_MIGRATE_ON_STARTUP_KEY)
|
||||
.unwrap_or_else(|err| {
|
||||
const DEFAULT_MIGRATE_ON_STARTUP: bool = true;
|
||||
warn!(
|
||||
"{} not set or invalid in configuration, defaulting to {}. Error: {}",
|
||||
DATABASE_MIGRATE_ON_STARTUP_KEY, DEFAULT_MIGRATE_ON_STARTUP, err
|
||||
);
|
||||
DEFAULT_MIGRATE_ON_STARTUP
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
apps/api/src/configs/key.rs
Normal file
9
apps/api/src/configs/key.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub(crate) const LOGGING_LEVEL_KEY: &str = "LOGGING.LEVEL";
|
||||
pub(crate) const LOGGING_UTC_KEY: &str = "LOGGING.UTC";
|
||||
//
|
||||
pub(crate) const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS";
|
||||
pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT";
|
||||
//
|
||||
pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL";
|
||||
pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS";
|
||||
pub(crate) const DATABASE_MIGRATE_ON_STARTUP_KEY: &str = "DATABASE.MIGRATION.MIGRATE_ON_STARTUP";
|
||||
52
apps/api/src/configs/logging.rs
Normal file
52
apps/api/src/configs/logging.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use config::{Config, ConfigError};
|
||||
use tracing::{Level, warn};
|
||||
|
||||
use super::{
|
||||
FromConfig,
|
||||
key::{LOGGING_LEVEL_KEY, LOGGING_UTC_KEY},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoggingSettings {
|
||||
pub level: Level,
|
||||
pub utc: bool,
|
||||
}
|
||||
|
||||
impl FromConfig for LoggingSettings {
|
||||
fn from_config(_config: &Config) -> Result<Self, String> {
|
||||
const DEFAULT_LOGGING_LEVEL: Level = Level::INFO;
|
||||
Ok(LoggingSettings {
|
||||
level: _config
|
||||
.get_string(LOGGING_LEVEL_KEY)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
"Failed to read {} from configuration, defaulting to {}. Error: {}",
|
||||
LOGGING_LEVEL_KEY, DEFAULT_LOGGING_LEVEL, err
|
||||
);
|
||||
DEFAULT_LOGGING_LEVEL.to_string()
|
||||
})
|
||||
.parse()
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
"Invalid logging level in configuration, defaulting to {}. Error: {}",
|
||||
DEFAULT_LOGGING_LEVEL, err
|
||||
);
|
||||
DEFAULT_LOGGING_LEVEL
|
||||
}),
|
||||
utc: _config
|
||||
.get_bool(LOGGING_UTC_KEY)
|
||||
.unwrap_or_else(|err: ConfigError| {
|
||||
const DEFAULT_UTC: bool = false;
|
||||
warn!(
|
||||
"Invalid UTC setting in configuration, defaulting to {}. Error: {}",
|
||||
DEFAULT_UTC, err
|
||||
);
|
||||
DEFAULT_UTC
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
56
apps/api/src/configs/server.rs
Normal file
56
apps/api/src/configs/server.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use config::{Config, ConfigError};
|
||||
use tracing::warn;
|
||||
|
||||
use super::{
|
||||
FromConfig,
|
||||
key::{SERVER_ADDRESS_KEY, SERVER_PORT_KEY},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerSettings {
|
||||
pub address: IpAddr,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl FromConfig for ServerSettings {
|
||||
fn from_config(_config: &Config) -> Result<Self, String> {
|
||||
Ok(ServerSettings {
|
||||
address: _config
|
||||
.get_string(SERVER_ADDRESS_KEY)
|
||||
.unwrap_or_else(|err| {
|
||||
const DEFAULT_ADDRESS: &str = "0.0.0.0";
|
||||
match err {
|
||||
ConfigError::NotFound(_) => {}
|
||||
_ => {
|
||||
warn!(
|
||||
"Failed to read {} from configuration, defaulting to {}. Error: {}",
|
||||
SERVER_ADDRESS_KEY, DEFAULT_ADDRESS, err
|
||||
);
|
||||
}
|
||||
};
|
||||
DEFAULT_ADDRESS.to_string()
|
||||
})
|
||||
.parse()
|
||||
.map_err(|e| format!("Invalid {} in configuration: {}", SERVER_ADDRESS_KEY, e))?,
|
||||
|
||||
port: _config.get_int(SERVER_PORT_KEY).unwrap_or_else(|err| {
|
||||
const DEFAULT_PORT: i64 = 8080;
|
||||
warn!(
|
||||
"{} not set or invalid in configuration, defaulting to {}. Error: {}",
|
||||
SERVER_PORT_KEY, DEFAULT_PORT, err
|
||||
);
|
||||
DEFAULT_PORT
|
||||
}) as u16,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
#[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
|
||||
if self.port == 0 || self.port > 65535 {
|
||||
return Err("Server port must be between 1 and 65535".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
apps/api/src/errors.rs
Normal file
1
apps/api/src/errors.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod service_error;
|
||||
15
apps/api/src/errors/service_error.rs
Normal file
15
apps/api/src/errors/service_error.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub type ServiceError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
#[allow(dead_code)] // TODO: remove when used
|
||||
pub trait IntoServiceError {
|
||||
fn into_service_error(self) -> ServiceError;
|
||||
}
|
||||
|
||||
impl<T> IntoServiceError for T
|
||||
where
|
||||
T: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_service_error(self) -> ServiceError {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
136
apps/api/src/main.rs
Normal file
136
apps/api/src/main.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
mod configs;
|
||||
mod errors;
|
||||
mod middlewares;
|
||||
mod routes;
|
||||
mod services;
|
||||
mod tasks;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::Router;
|
||||
use database::get_connection;
|
||||
use sea_orm::ConnectOptions;
|
||||
use tracing::{debug, info};
|
||||
use tracing_subscriber::fmt::format::{DefaultFields, Format};
|
||||
|
||||
use crate::{
|
||||
configs::{ProgramSettings, get_program_settings, logging::LoggingSettings},
|
||||
routes::{AppService, AppState},
|
||||
services::settings::SettingsService,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Temporary subscriber for initial logging during configuration reading
|
||||
let make_temporary_subscriber = || {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.with_target(false)
|
||||
.with_level(true)
|
||||
.finish()
|
||||
};
|
||||
|
||||
let settings =
|
||||
tracing::subscriber::with_default(make_temporary_subscriber(), || -> ProgramSettings {
|
||||
debug!("Temporary subscriber installed.");
|
||||
info!("Reading configuration...");
|
||||
let settings = get_program_settings();
|
||||
info!("Configuration read successfully.");
|
||||
debug!("Resetting global subscriber...");
|
||||
|
||||
let subscriber = get_global_tracing_subscriber_builder(&settings.logging).finish();
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.expect("Failed to set global default subscriber");
|
||||
|
||||
debug!(
|
||||
"Global subscriber set with logging level: {:?}",
|
||||
settings.logging.level
|
||||
);
|
||||
|
||||
settings
|
||||
});
|
||||
|
||||
tasks::startup::run_startup_tasks(&settings)
|
||||
.await
|
||||
.expect("Failed to run startup tasks");
|
||||
|
||||
// setup database connection pool
|
||||
info!("Establishing database connection...");
|
||||
debug!("Database URL: {}", settings.database.url);
|
||||
|
||||
let db_options = |options: &mut ConnectOptions| {
|
||||
options.max_connections(settings.database.max_connections);
|
||||
};
|
||||
|
||||
let db_connection = Arc::new(
|
||||
get_connection(&settings.database.url, Some(db_options))
|
||||
.await
|
||||
.expect("Failed to establish database connection"),
|
||||
);
|
||||
|
||||
info!("Database connection established.");
|
||||
|
||||
// build the axum app and run the server...
|
||||
info!("Starting application...");
|
||||
let app: Router = routes::get_root_router(Arc::new(get_app_state(&db_connection)));
|
||||
|
||||
let address = format!("{}:{}", settings.server.address, settings.server.port);
|
||||
info!("Starting server at http://{}", address);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(address)
|
||||
.await
|
||||
.expect("Failed to bind to address");
|
||||
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.expect("Failed to run the server");
|
||||
}
|
||||
|
||||
fn get_global_tracing_subscriber_builder(
|
||||
settings: &LoggingSettings,
|
||||
) -> tracing_subscriber::fmt::SubscriberBuilder<
|
||||
DefaultFields,
|
||||
Format<tracing_subscriber::fmt::format::Full, BoxedTimer>,
|
||||
> {
|
||||
// After configuration is read, install the global subscriber
|
||||
let builder = tracing_subscriber::fmt()
|
||||
.with_max_level(settings.level)
|
||||
.with_target(false)
|
||||
.with_level(true);
|
||||
|
||||
if settings.utc {
|
||||
builder.with_timer(BoxedTimer(Box::new(
|
||||
tracing_subscriber::fmt::time::UtcTime::rfc_3339(),
|
||||
)))
|
||||
} else {
|
||||
builder.with_timer(BoxedTimer(Box::new(
|
||||
tracing_subscriber::fmt::time::ChronoLocal::rfc_3339(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_app_state(db_connection: &Arc<sea_orm::DatabaseConnection>) -> AppState {
|
||||
AppState {
|
||||
database_connection: db_connection.clone(),
|
||||
service: Arc::new(AppService {
|
||||
settings: Arc::new(SettingsService::new(db_connection.clone())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// A small wrapper that holds a boxed `FormatTime` trait object and itself
|
||||
// implements `FormatTime`, allowing us to use it as a concrete type with
|
||||
// `builder.with_timer` while still picking the concrete timer implementation
|
||||
// at runtime.
|
||||
// wrapper type to hold boxed timers and implement the `FormatTime` trait for
|
||||
// a concrete type so `with_timer` may be called once outside the conditional.
|
||||
struct BoxedTimer(Box<dyn tracing_subscriber::fmt::time::FormatTime + Send + Sync + 'static>);
|
||||
|
||||
impl tracing_subscriber::fmt::time::FormatTime for BoxedTimer {
|
||||
fn format_time(
|
||||
&self,
|
||||
w: &mut tracing_subscriber::fmt::format::Writer<'_>,
|
||||
) -> std::result::Result<(), std::fmt::Error> {
|
||||
self.0.format_time(w)
|
||||
}
|
||||
}
|
||||
34
apps/api/src/middlewares.rs
Normal file
34
apps/api/src/middlewares.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use axum::{
|
||||
BoxError, Router,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::{Method, StatusCode, Uri},
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tower::{ServiceBuilder, timeout::TimeoutLayer};
|
||||
|
||||
use tracing::warn;
|
||||
|
||||
pub const TIMEOUT_DURATION_SECS: u64 = 30;
|
||||
|
||||
pub fn apply_root_middleware(router: Router) -> Router {
|
||||
let timeout_layer = TimeoutLayer::new(Duration::from_secs(TIMEOUT_DURATION_SECS));
|
||||
|
||||
let service_builder = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(handle_timeout_error))
|
||||
.layer(timeout_layer);
|
||||
|
||||
router.layer(service_builder)
|
||||
}
|
||||
|
||||
pub async fn handle_timeout_error(
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
//
|
||||
err: BoxError,
|
||||
) -> (StatusCode, String) {
|
||||
warn!("`{method} {uri}` failed with {err}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Internal server error".to_string(),
|
||||
)
|
||||
}
|
||||
44
apps/api/src/routes.rs
Normal file
44
apps/api/src/routes.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Extension, Router};
|
||||
use migration::sea_orm::DatabaseConnection;
|
||||
|
||||
use crate::{middlewares, services::settings::SettingsStore};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
// TODO: remove dead_code allowances when fields are used
|
||||
#[allow(dead_code)]
|
||||
pub database_connection: Arc<DatabaseConnection>,
|
||||
// TODO: remove dead_code allowances when fields are used
|
||||
#[allow(dead_code)]
|
||||
pub service: Arc<AppService>,
|
||||
}
|
||||
|
||||
pub type ServiceState<T> = Arc<T>;
|
||||
|
||||
pub struct AppService {
|
||||
#[allow(dead_code)] // TODO: remove when used
|
||||
pub settings: ServiceState<dyn SettingsStore>,
|
||||
}
|
||||
|
||||
pub fn get_root_router(state: impl Into<Arc<AppState>>) -> Router {
|
||||
let mut router = Router::new();
|
||||
|
||||
router = middlewares::apply_root_middleware(router);
|
||||
|
||||
router = router.layer(Extension(state.into()));
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ensure_state_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<AppState>();
|
||||
}
|
||||
}
|
||||
1
apps/api/src/services.rs
Normal file
1
apps/api/src/services.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod settings;
|
||||
92
apps/api/src/services/settings.rs
Normal file
92
apps/api/src/services/settings.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use database::generated::entities::config::{self, ActiveModel as ConfigActiveModel};
|
||||
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, DbErr, EntityTrait,
|
||||
IntoActiveModel, QueryFilter,
|
||||
};
|
||||
|
||||
use crate::errors::service_error::{IntoServiceError, ServiceError};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait SettingsStore: Send + Sync {
|
||||
#[allow(dead_code)] // TODO: remove when used
|
||||
async fn get_setting(&self, key: &str) -> Result<String, ServiceError>;
|
||||
#[allow(dead_code)] // TODO: remove when used
|
||||
async fn set_setting(&self, key: &str, value: String) -> Result<(), ServiceError>;
|
||||
}
|
||||
|
||||
pub struct SettingsService {
|
||||
#[allow(dead_code)] // TODO: remove when used
|
||||
connection: Arc<DatabaseConnection>,
|
||||
}
|
||||
|
||||
impl SettingsService {
|
||||
pub fn new(connection: Arc<DatabaseConnection>) -> Self {
|
||||
Self { connection }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SettingsStore for SettingsService {
|
||||
async fn get_setting(&self, key: &str) -> Result<String, ServiceError> {
|
||||
let setting = config::Entity::find()
|
||||
.filter(config::Column::Key.eq(key))
|
||||
.one(&*self.connection)
|
||||
.await;
|
||||
|
||||
match setting {
|
||||
Err(err) => Err(err.into_service_error()),
|
||||
Ok(None) => Err(
|
||||
DbErr::RecordNotFound(format!("Setting with key '{}' not found", key))
|
||||
.into_service_error(),
|
||||
),
|
||||
Ok(Some(record)) => Ok(record.value),
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_setting(&self, key: &str, value: String) -> Result<(), ServiceError> {
|
||||
let existing = config::Entity::find()
|
||||
.filter(config::Column::Key.eq(key))
|
||||
.one(&*self.connection)
|
||||
.await;
|
||||
|
||||
let handle_not_found = async |key: String, value: String| {
|
||||
let new_record = ConfigActiveModel {
|
||||
key: ActiveValue::Set(key),
|
||||
value: ActiveValue::Set(value),
|
||||
created_at: ActiveValue::Set(chrono::Utc::now()),
|
||||
updated_at: ActiveValue::Set(chrono::Utc::now()),
|
||||
};
|
||||
new_record
|
||||
.insert(&*self.connection)
|
||||
.await
|
||||
.map_err(|err| err.into_service_error())
|
||||
};
|
||||
|
||||
match existing {
|
||||
Err(err) => match err {
|
||||
DbErr::RecordNotFound(_) => {
|
||||
handle_not_found(key.to_string(), value).await?;
|
||||
}
|
||||
_ => {
|
||||
return Err(Box::new(err));
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
handle_not_found(key.to_string(), value).await?;
|
||||
}
|
||||
Ok(Some(mut record)) => {
|
||||
record.value = value;
|
||||
record
|
||||
.into_active_model()
|
||||
.update(&*self.connection)
|
||||
.await
|
||||
.map_err(|err| err.into_service_error())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
apps/api/src/tasks.rs
Normal file
1
apps/api/src/tasks.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod startup;
|
||||
25
apps/api/src/tasks/startup.rs
Normal file
25
apps/api/src/tasks/startup.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use migration::migrate_database;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::configs::ProgramSettings;
|
||||
|
||||
pub async fn run_startup_tasks(config: &ProgramSettings) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Here you can add any startup tasks you want to run when the application starts.
|
||||
info!("Running startup tasks...");
|
||||
if config.database.migrate_on_startup {
|
||||
run_database_migrations(&config.database.url).await?;
|
||||
} else {
|
||||
info!("Database migration on startup is disabled. Skipping migration.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_database_migrations(db_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Logic to run database migrations
|
||||
info!("Running database migrations...");
|
||||
debug!("Database URL: {}", db_url);
|
||||
migrate_database(db_url).await.map_err(Box::new)?;
|
||||
info!("Database migrations completed.");
|
||||
Ok(())
|
||||
}
|
||||
@@ -15,3 +15,4 @@ tokio = { version = "1.47.0", features = ["full"] }
|
||||
url = "2.5.7"
|
||||
clap = { version = "4.5.48", features = ["derive", "env"] }
|
||||
path-clean = "1.0.1"
|
||||
serde_json = "1.0.145"
|
||||
|
||||
@@ -13,24 +13,151 @@ pub struct EnvFile {
|
||||
pub file_type: EnvFileType,
|
||||
pub db_type: DBType,
|
||||
pub db_url: String,
|
||||
//
|
||||
buffer: serde_json::Value,
|
||||
}
|
||||
|
||||
impl EnvFile {
|
||||
pub fn write(self, path: impl AsRef<std::path::Path>) {
|
||||
let path_ref = path.as_ref();
|
||||
println!("Config file path: {}", path_ref.display());
|
||||
let mut config_file =
|
||||
std::fs::File::create(path_ref).expect("Failed to create config file");
|
||||
//
|
||||
self._write_line(&mut config_file, "DB_TYPE", &self.db_type.to_string());
|
||||
self._write_line(&mut config_file, "DATABASE_URL", &self.db_url.to_string())
|
||||
pub fn new(file_type: EnvFileType, db_type: DBType, db_url: String) -> Self {
|
||||
let mut env_file = EnvFile {
|
||||
file_type,
|
||||
db_type,
|
||||
db_url,
|
||||
buffer: serde_json::Value::Object(serde_json::Map::new()),
|
||||
};
|
||||
|
||||
env_file._write_line_buffer("DATABASE__TYPE", &env_file.db_type.to_string());
|
||||
env_file._write_line_buffer("DATABASE__URL", &env_file.db_url.to_string());
|
||||
|
||||
env_file
|
||||
}
|
||||
|
||||
fn _write_line(&self, file: &mut std::fs::File, key: &str, value: &str) {
|
||||
match self.file_type {
|
||||
EnvFileType::DotEnv => writeln!(file, "{}={}", key, value),
|
||||
EnvFileType::Yaml => writeln!(file, "{}: \"{}\"", key, value),
|
||||
pub fn write(&mut self, stream: &mut dyn Write, with_prefix: bool) {
|
||||
self._write_buffer(stream, with_prefix);
|
||||
}
|
||||
.expect("Failed to write to config file");
|
||||
|
||||
fn key_into_buffer_key(&self, key: &str) -> Vec<String> {
|
||||
key.split("__").map(String::from).collect()
|
||||
}
|
||||
|
||||
fn _write_line_buffer(&mut self, key: &str, value: &str) {
|
||||
let buffer_key = self.key_into_buffer_key(key);
|
||||
let mut current = &mut self.buffer;
|
||||
for k in &buffer_key[0..(buffer_key.len() - 1)] {
|
||||
if current.get(k).is_none() {
|
||||
current[k] = serde_json::Value::Object(serde_json::Map::new());
|
||||
}
|
||||
current = &mut current[k];
|
||||
}
|
||||
current[buffer_key.last().unwrap()] = serde_json::Value::String(value.to_string());
|
||||
}
|
||||
|
||||
fn _write_buffer(&self, file: &mut dyn Write, with_prefix: bool) {
|
||||
match self.file_type {
|
||||
EnvFileType::DotEnv => self._write_buffer_env(file, with_prefix),
|
||||
EnvFileType::Yaml => self._write_buffer_yaml(file),
|
||||
}
|
||||
}
|
||||
|
||||
fn _write_buffer_env(&self, file: &mut dyn Write, with_prefix: bool) {
|
||||
fn _write_buffer_env_layer(
|
||||
file: &mut dyn Write,
|
||||
buffer: &serde_json::Value,
|
||||
prefix: String,
|
||||
with_root_prefix: bool,
|
||||
) {
|
||||
if let serde_json::Value::Object(map) = buffer {
|
||||
for (key, value) in map {
|
||||
let current_key = if prefix.is_empty() {
|
||||
if with_root_prefix {
|
||||
format!("YANPM__{}", key)
|
||||
} else {
|
||||
key.to_string()
|
||||
}
|
||||
} else {
|
||||
format!("{}__{}", prefix, key)
|
||||
};
|
||||
match value {
|
||||
serde_json::Value::Object(_) => {
|
||||
_write_buffer_env_layer(file, value, current_key, with_root_prefix);
|
||||
}
|
||||
_ => {
|
||||
writeln!(file, "{}={}", current_key, value).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_write_buffer_env_layer(file, &self.buffer, String::new(), with_prefix);
|
||||
}
|
||||
|
||||
fn _write_buffer_yaml(&self, file: &mut dyn Write) {
|
||||
let mut layer = 0;
|
||||
fn _write_buffer_yaml_layer(
|
||||
file: &mut dyn Write,
|
||||
buffer: &serde_json::Value,
|
||||
layer: &mut usize,
|
||||
) {
|
||||
if let serde_json::Value::Object(map) = buffer {
|
||||
for (key, value) in map {
|
||||
let indent = " ".repeat(*layer);
|
||||
match value {
|
||||
serde_json::Value::Object(_) => {
|
||||
writeln!(file, "{}{}:", indent, key).unwrap();
|
||||
*layer += 1;
|
||||
_write_buffer_yaml_layer(file, value, layer);
|
||||
*layer -= 1;
|
||||
}
|
||||
_ => {
|
||||
writeln!(file, "{}{}: {}", indent, key, value).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_write_buffer_yaml_layer(file, &self.buffer, &mut layer);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_env_file_write_yaml() {
|
||||
let mut env_file_nested = EnvFile::new(
|
||||
EnvFileType::Yaml,
|
||||
DBType::SQLite,
|
||||
"mysql://user:pass@localhost/db".to_string(),
|
||||
);
|
||||
|
||||
let mut output_stream = Vec::new();
|
||||
env_file_nested.write(&mut output_stream, false);
|
||||
let output_string = String::from_utf8(output_stream).unwrap();
|
||||
let expected_output = "\
|
||||
DATABASE:
|
||||
TYPE: \"SQLite\"
|
||||
URL: \"mysql://user:pass@localhost/db\"
|
||||
";
|
||||
assert_eq!(output_string, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_env_file_write_env() {
|
||||
let mut env_file_nested = EnvFile::new(
|
||||
EnvFileType::DotEnv,
|
||||
DBType::PostgreSQL,
|
||||
"postgres://user:pass@localhost/db".to_string(),
|
||||
);
|
||||
let mut output_stream = Vec::new();
|
||||
env_file_nested.write(&mut output_stream, true);
|
||||
let output_string = String::from_utf8(output_stream).unwrap();
|
||||
let expected_output = "\
|
||||
YANPM__DATABASE__TYPE=\"PostgreSQL\"
|
||||
YANPM__DATABASE__URL=\"postgres://user:pass@localhost/db\"
|
||||
";
|
||||
assert_eq!(output_string, expected_output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,17 +29,18 @@ pub fn write_env_files(db_config: &DBConfigInfoType) {
|
||||
DBConfigInfoType::PreExisting(config) => (config.db_type.clone(), config.url.clone()),
|
||||
};
|
||||
|
||||
let api_env_file = EnvFile {
|
||||
file_type: env::EnvFileType::Yaml,
|
||||
db_type,
|
||||
db_url,
|
||||
};
|
||||
let mut api_env = EnvFile::new(env::EnvFileType::Yaml, db_type, db_url);
|
||||
let mut db_env = api_env.clone();
|
||||
db_env.file_type = env::EnvFileType::DotEnv;
|
||||
|
||||
let mut db_env_file = api_env_file.clone();
|
||||
db_env_file.file_type = env::EnvFileType::DotEnv;
|
||||
let mut api_file =
|
||||
std::fs::File::create(&api_config_path_absolute).expect("Failed to create API config file");
|
||||
|
||||
api_env_file.write(&api_config_path_absolute);
|
||||
db_env_file.write(&db_config_path_absolute);
|
||||
let mut db_file =
|
||||
std::fs::File::create(&db_config_path_absolute).expect("Failed to create DB config file");
|
||||
|
||||
api_env.write(&mut api_file, true);
|
||||
db_env.write(&mut db_file, false);
|
||||
}
|
||||
|
||||
pub async fn stop_container(
|
||||
|
||||
@@ -13,7 +13,8 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.47.0", features = ["full"] }
|
||||
sea-orm = { version = "2.0.0-rc", features = [ "sqlx-postgres", "sqlx-mysql", "sqlx-sqlite", "runtime-tokio-rustls", "macros", "mock", "with-chrono", "with-json", "with-uuid", "sqlite-use-returning-for-3_35", "mariadb-use-returning" ] }
|
||||
sea-orm = { workspace = true, features = [ "sqlx-postgres", "sqlx-mysql", "sqlx-sqlite", "runtime-tokio-rustls", "macros", "mock", "with-chrono", "with-json", "with-uuid", "sqlite-use-returning-for-3_35", "mariadb-use-returning" ] }
|
||||
log = "0.4.28"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use sea_orm::ConnectOptions;
|
||||
use log::LevelFilter;
|
||||
use sea_orm::ConnectOptions;
|
||||
pub mod generated;
|
||||
|
||||
pub async fn get_connection<T: FnOnce(&mut ConnectOptions)>(
|
||||
@@ -13,7 +14,8 @@ pub async fn get_connection<T: FnOnce(&mut ConnectOptions)>(
|
||||
.connect_timeout(std::time::Duration::from_secs(8))
|
||||
.idle_timeout(std::time::Duration::from_secs(8))
|
||||
.test_before_acquire(true)
|
||||
.sqlx_logging(true);
|
||||
.sqlx_logging(true)
|
||||
.sqlx_logging_level(LevelFilter::Debug);
|
||||
|
||||
if let Some(option_fn) = option_fn {
|
||||
option_fn(&mut opt);
|
||||
|
||||
@@ -11,10 +11,11 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
sea-orm-cli = { version = "2.0.0-rc", features = ["sqlx-postgres", "sqlx-mysql", "sqlx-sqlite", "runtime-tokio"] }
|
||||
sea-orm-cli = { workspace = true, features = ["sqlx-postgres", "sqlx-mysql", "sqlx-sqlite", "runtime-tokio"] }
|
||||
log = "0.4.28"
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "2.0.0-rc"
|
||||
workspace = true
|
||||
features = [
|
||||
"runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
|
||||
"sqlx-postgres", "sqlx-mysql", "sqlx-sqlite" # `DATABASE_DRIVER` features
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod migrations;
|
||||
use migrations::*;
|
||||
use sea_orm_migration::sea_orm::Database;
|
||||
use sea_orm_migration::sea_orm::{ConnectOptions, Database};
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -17,7 +17,16 @@ impl MigratorTrait for Migrator {
|
||||
}
|
||||
|
||||
pub async fn migrate_database(db_url: &str) -> Result<(), DbErr> {
|
||||
let db = Database::connect(db_url).await?;
|
||||
let mut opt = ConnectOptions::new(db_url);
|
||||
opt.max_connections(10)
|
||||
.min_connections(0)
|
||||
.connect_timeout(std::time::Duration::from_secs(8))
|
||||
.idle_timeout(std::time::Duration::from_secs(8))
|
||||
.test_before_acquire(true)
|
||||
.sqlx_logging(true)
|
||||
.sqlx_logging_level(log::LevelFilter::Debug);
|
||||
let db = Database::connect(opt).await?;
|
||||
|
||||
Migrator::up(&db, None).await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user