diff --git a/.gitignore b/.gitignore index 87af617..146633d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ target #.idea/ # generated environment variables file +.env .env.generated + +generated-config.yaml diff --git a/Cargo.lock b/Cargo.lock index 9fec9ff..f9f0ad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index d44d56d..9c3e719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/apps/api/.gitignore b/apps/api/.gitignore new file mode 100644 index 0000000..a539470 --- /dev/null +++ b/apps/api/.gitignore @@ -0,0 +1 @@ +config.yaml \ No newline at end of file diff --git a/apps/api/.gitkeep b/apps/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/api/Cargo.toml b/apps/api/Cargo.toml new file mode 100644 index 0000000..d5c120c --- /dev/null +++ b/apps/api/Cargo.toml @@ -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 } diff --git a/apps/api/src/configs.rs b/apps/api/src/configs.rs new file mode 100644 index 0000000..cae85de --- /dev/null +++ b/apps/api/src/configs.rs @@ -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; + 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 { + 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") +} diff --git a/apps/api/src/configs/database.rs b/apps/api/src/configs/database.rs new file mode 100644 index 0000000..25d9a78 --- /dev/null +++ b/apps/api/src/configs/database.rs @@ -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 { + 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(()) + } +} diff --git a/apps/api/src/configs/key.rs b/apps/api/src/configs/key.rs new file mode 100644 index 0000000..dd31902 --- /dev/null +++ b/apps/api/src/configs/key.rs @@ -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"; diff --git a/apps/api/src/configs/logging.rs b/apps/api/src/configs/logging.rs new file mode 100644 index 0000000..1aa47b9 --- /dev/null +++ b/apps/api/src/configs/logging.rs @@ -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 { + 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(()) + } +} diff --git a/apps/api/src/configs/server.rs b/apps/api/src/configs/server.rs new file mode 100644 index 0000000..16e6bee --- /dev/null +++ b/apps/api/src/configs/server.rs @@ -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 { + 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(()) + } +} diff --git a/apps/api/src/errors.rs b/apps/api/src/errors.rs new file mode 100644 index 0000000..ae13070 --- /dev/null +++ b/apps/api/src/errors.rs @@ -0,0 +1 @@ +pub mod service_error; diff --git a/apps/api/src/errors/service_error.rs b/apps/api/src/errors/service_error.rs new file mode 100644 index 0000000..20a5835 --- /dev/null +++ b/apps/api/src/errors/service_error.rs @@ -0,0 +1,15 @@ +pub type ServiceError = Box; + +#[allow(dead_code)] // TODO: remove when used +pub trait IntoServiceError { + fn into_service_error(self) -> ServiceError; +} + +impl IntoServiceError for T +where + T: std::error::Error + Send + Sync + 'static, +{ + fn into_service_error(self) -> ServiceError { + Box::new(self) + } +} diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs new file mode 100644 index 0000000..dd1af30 --- /dev/null +++ b/apps/api/src/main.rs @@ -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, +> { + // 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) -> 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); + +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) + } +} diff --git a/apps/api/src/middlewares.rs b/apps/api/src/middlewares.rs new file mode 100644 index 0000000..f47e3de --- /dev/null +++ b/apps/api/src/middlewares.rs @@ -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(), + ) +} diff --git a/apps/api/src/routes.rs b/apps/api/src/routes.rs new file mode 100644 index 0000000..c0d22db --- /dev/null +++ b/apps/api/src/routes.rs @@ -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, + // TODO: remove dead_code allowances when fields are used + #[allow(dead_code)] + pub service: Arc, +} + +pub type ServiceState = Arc; + +pub struct AppService { + #[allow(dead_code)] // TODO: remove when used + pub settings: ServiceState, +} + +pub fn get_root_router(state: impl Into>) -> 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() {} + assert_send_sync::(); + } +} diff --git a/apps/api/src/services.rs b/apps/api/src/services.rs new file mode 100644 index 0000000..6e98cef --- /dev/null +++ b/apps/api/src/services.rs @@ -0,0 +1 @@ +pub mod settings; diff --git a/apps/api/src/services/settings.rs b/apps/api/src/services/settings.rs new file mode 100644 index 0000000..847db61 --- /dev/null +++ b/apps/api/src/services/settings.rs @@ -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; + #[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, +} + +impl SettingsService { + pub fn new(connection: Arc) -> Self { + Self { connection } + } +} + +#[async_trait::async_trait] +impl SettingsStore for SettingsService { + async fn get_setting(&self, key: &str) -> Result { + 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(()) + } +} diff --git a/apps/api/src/tasks.rs b/apps/api/src/tasks.rs new file mode 100644 index 0000000..564d8c8 --- /dev/null +++ b/apps/api/src/tasks.rs @@ -0,0 +1 @@ +pub mod startup; diff --git a/apps/api/src/tasks/startup.rs b/apps/api/src/tasks/startup.rs new file mode 100644 index 0000000..17d150b --- /dev/null +++ b/apps/api/src/tasks/startup.rs @@ -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> { + // 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> { + // 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(()) +} diff --git a/apps/container/Cargo.toml b/apps/container/Cargo.toml index 6b53abc..bc502e1 100644 --- a/apps/container/Cargo.toml +++ b/apps/container/Cargo.toml @@ -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" diff --git a/apps/container/src/env.rs b/apps/container/src/env.rs index ec1822c..162883f 100644 --- a/apps/container/src/env.rs +++ b/apps/container/src/env.rs @@ -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) { - 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); + } + + fn key_into_buffer_key(&self, key: &str) -> Vec { + 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]; } - .expect("Failed to write to config file"); + 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); } } diff --git a/apps/container/src/util.rs b/apps/container/src/util.rs index 9783ae1..d513904 100644 --- a/apps/container/src/util.rs +++ b/apps/container/src/util.rs @@ -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( diff --git a/public/database/Cargo.toml b/public/database/Cargo.toml index 86c0b16..76064c9 100644 --- a/public/database/Cargo.toml +++ b/public/database/Cargo.toml @@ -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 diff --git a/public/database/src/lib.rs b/public/database/src/lib.rs index ebfa1f8..fb047b5 100644 --- a/public/database/src/lib.rs +++ b/public/database/src/lib.rs @@ -1,4 +1,5 @@ -pub use sea_orm::ConnectOptions; +use log::LevelFilter; +use sea_orm::ConnectOptions; pub mod generated; pub async fn get_connection( @@ -13,7 +14,8 @@ pub async fn get_connection( .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); diff --git a/public/migration/Cargo.toml b/public/migration/Cargo.toml index 8241205..ed761e5 100644 --- a/public/migration/Cargo.toml +++ b/public/migration/Cargo.toml @@ -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 diff --git a/public/migration/src/lib.rs b/public/migration/src/lib.rs index 7488856..1be42ae 100644 --- a/public/migration/src/lib.rs +++ b/public/migration/src/lib.rs @@ -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 }