diff --git a/Cargo.lock b/Cargo.lock
index ec5d2f1a..a668ac4f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,7 +8,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
- "bitflags",
+ "bitflags 2.6.0",
"bytes",
"futures-core",
"futures-sink",
@@ -29,7 +29,7 @@ dependencies = [
"actix-service",
"actix-utils",
"actix-web",
- "bitflags",
+ "bitflags 2.6.0",
"bytes",
"derive_more 0.99.18",
"futures-core",
@@ -54,7 +54,7 @@ dependencies = [
"actix-utils",
"ahash",
"base64 0.22.1",
- "bitflags",
+ "bitflags 2.6.0",
"brotli",
"bytes",
"bytestring",
@@ -128,7 +128,7 @@ dependencies = [
"futures-core",
"futures-util",
"mio",
- "socket2",
+ "socket2 0.5.7",
"tokio",
"tracing",
]
@@ -208,7 +208,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"smallvec",
- "socket2",
+ "socket2 0.5.7",
"time",
"url",
]
@@ -309,6 +309,12 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
@@ -400,6 +406,196 @@ version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
+[[package]]
+name = "async-channel"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand 2.2.0",
+ "futures-lite 2.5.0",
+ "slab",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
+dependencies = [
+ "async-channel 2.3.1",
+ "async-executor",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "blocking",
+ "futures-lite 2.5.0",
+ "once_cell",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.27",
+ "slab",
+ "socket2 0.4.10",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
+dependencies = [
+ "async-lock 3.4.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.5.0",
+ "parking",
+ "polling 3.7.4",
+ "rustix 0.38.40",
+ "slab",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
+dependencies = [
+ "event-listener 5.3.1",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-std"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615"
+dependencies = [
+ "async-channel 1.9.0",
+ "async-global-executor",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite 2.5.0",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -433,11 +629,26 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+dependencies = [
+ "serde",
+]
[[package]]
name = "block-buffer"
@@ -448,6 +659,19 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "blocking"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
+dependencies = [
+ "async-channel 2.3.1",
+ "async-task",
+ "futures-io",
+ "futures-lite 2.5.0",
+ "piper",
+]
+
[[package]]
name = "brotli"
version = "6.0.0"
@@ -632,6 +856,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609"
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -656,6 +895,16 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -671,6 +920,21 @@ dependencies = [
"libc",
]
+[[package]]
+name = "crc"
+version = "3.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
[[package]]
name = "crc32fast"
version = "1.4.2"
@@ -708,6 +972,15 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
@@ -734,6 +1007,51 @@ dependencies = [
"cipher",
]
+[[package]]
+name = "darling"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
[[package]]
name = "deranged"
version = "0.3.11"
@@ -790,6 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
"subtle",
]
@@ -805,6 +1124,12 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
[[package]]
name = "drust"
version = "0.0.3"
@@ -813,6 +1138,15 @@ dependencies = [
"pagetop-bootsier",
]
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "encoding_rs"
version = "0.8.35"
@@ -838,6 +1172,59 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+dependencies = [
+ "event-listener 5.3.1",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
+
[[package]]
name = "figlet-rs"
version = "0.1.5"
@@ -928,6 +1315,8 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
+ "futures-core",
+ "futures-sink",
"spin",
]
@@ -937,6 +1326,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -946,12 +1350,104 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-lite"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
+dependencies = [
+ "fastrand 2.2.0",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "futures-sink"
version = "0.3.31"
@@ -970,8 +1466,13 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
+ "futures-channel",
"futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -1041,11 +1542,23 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
- "bitflags",
+ "bitflags 2.6.0",
"ignore",
"walkdir",
]
+[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "grass"
version = "0.13.4"
@@ -1106,12 +1619,45 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+[[package]]
+name = "hashlink"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
+dependencies = [
+ "hashbrown 0.14.5",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
[[package]]
name = "hkdf"
version = "0.12.4"
@@ -1130,6 +1676,15 @@ dependencies = [
"digest",
]
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "http"
version = "0.2.12"
@@ -1309,6 +1864,12 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "1.0.3"
@@ -1381,6 +1942,17 @@ dependencies = [
"hashbrown 0.15.1",
]
+[[package]]
+name = "inherent"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "inout"
version = "0.1.3"
@@ -1390,6 +1962,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "intl-memoizer"
version = "0.5.2"
@@ -1409,12 +1990,32 @@ dependencies = [
"unic-langid",
]
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "1.0.11"
@@ -1439,6 +2040,15 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
[[package]]
name = "language-tags"
version = "0.3.2"
@@ -1459,6 +2069,9 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
[[package]]
name = "libc"
@@ -1472,6 +2085,23 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@@ -1516,6 +2146,9 @@ name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+dependencies = [
+ "value-bag",
+]
[[package]]
name = "matchers"
@@ -1526,6 +2159,16 @@ dependencies = [
"regex-automata 0.1.10",
]
+[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
[[package]]
name = "memchr"
version = "2.7.4"
@@ -1569,7 +2212,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.3.9",
"libc",
"log",
"wasi",
@@ -1582,6 +2225,23 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
+[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -1602,12 +2262,49 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand",
+ "smallvec",
+ "zeroize",
+]
+
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1615,6 +2312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
@@ -1638,6 +2336,84 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+[[package]]
+name = "openssl"
+version = "0.10.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordered-float"
+version = "3.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd"
+dependencies = [
+ "heck 0.4.1",
+ "itertools",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "overload"
version = "0.1.1"
@@ -1714,6 +2490,26 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "pagetop-seaorm"
+version = "0.0.1"
+dependencies = [
+ "async-trait",
+ "futures",
+ "pagetop",
+ "sea-orm",
+ "sea-schema",
+ "serde",
+ "static-files",
+ "url",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -1767,6 +2563,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -1902,12 +2707,75 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "piper"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.2.0",
+ "futures-io",
+]
+
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi 0.4.0",
+ "pin-project-lite",
+ "rustix 0.38.40",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "polyval"
version = "0.6.2"
@@ -1968,6 +2836,28 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@@ -1983,6 +2873,19 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "version_check",
+ "yansi",
+]
+
[[package]]
name = "quote"
version = "1.0.37"
@@ -2028,7 +2931,7 @@ version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
- "bitflags",
+ "bitflags 2.6.0",
]
[[package]]
@@ -2081,6 +2984,26 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "rsa"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -2102,16 +3025,30 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "rustix"
version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [
- "bitflags",
+ "bitflags 2.6.0",
"errno",
"libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.4.14",
"windows-sys 0.52.0",
]
@@ -2130,12 +3067,151 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "sea-bae"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "sea-orm"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5680a8b686985116607ef5f5af2b1f9e1cc2c228330e93101816a0baa279afa"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "futures",
+ "log",
+ "ouroboros",
+ "sea-orm-macros",
+ "sea-query",
+ "sea-query-binder",
+ "serde",
+ "sqlx",
+ "strum",
+ "thiserror",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "sea-orm-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "sea-bae",
+ "syn 2.0.87",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sea-query"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb"
+dependencies = [
+ "inherent",
+ "ordered-float",
+ "sea-query-derive",
+]
+
+[[package]]
+name = "sea-query-binder"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608"
+dependencies = [
+ "sea-query",
+ "sqlx",
+]
+
+[[package]]
+name = "sea-query-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839"
+dependencies = [
+ "darling",
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "thiserror",
+]
+
+[[package]]
+name = "sea-schema"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aab1592d17860a9a8584d9b549aebcd06f7bdc3ff615f71752486ba0b05b1e6e"
+dependencies = [
+ "futures",
+ "sea-query",
+ "sea-schema-derive",
+]
+
+[[package]]
+name = "sea-schema-derive"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "self_cell"
version = "0.10.3"
@@ -2256,6 +3332,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core",
+]
+
[[package]]
name = "siphasher"
version = "0.3.11"
@@ -2286,6 +3372,19 @@ name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
[[package]]
name = "socket2"
@@ -2306,6 +3405,221 @@ dependencies = [
"lock_api",
]
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlformat"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
+dependencies = [
+ "nom",
+ "unicode_categories",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
+dependencies = [
+ "async-io 1.13.0",
+ "async-std",
+ "atoi",
+ "byteorder",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener 5.3.1",
+ "futures-channel",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.14.5",
+ "hashlink",
+ "hex",
+ "indexmap",
+ "log",
+ "memchr",
+ "native-tls",
+ "once_cell",
+ "paste",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlformat",
+ "thiserror",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
+dependencies = [
+ "async-std",
+ "dotenvy",
+ "either",
+ "heck 0.5.0",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn 2.0.87",
+ "tempfile",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bitflags 2.6.0",
+ "byteorder",
+ "bytes",
+ "crc",
+ "digest",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand",
+ "rsa",
+ "serde",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bitflags 2.6.0",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
+dependencies = [
+ "atoi",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "tracing",
+ "url",
+]
+
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -2323,12 +3637,35 @@ dependencies = [
"path-slash",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "strum"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+
[[package]]
name = "substring"
version = "1.4.5"
@@ -2376,6 +3713,19 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "tempfile"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
+dependencies = [
+ "cfg-if",
+ "fastrand 2.2.0",
+ "once_cell",
+ "rustix 0.38.40",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "tera"
version = "1.20.0"
@@ -2404,7 +3754,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
dependencies = [
- "rustix",
+ "rustix 0.38.40",
"windows-sys 0.59.0",
]
@@ -2479,6 +3829,21 @@ dependencies = [
"zerovec",
]
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
[[package]]
name = "tokio"
version = "1.41.1"
@@ -2492,7 +3857,7 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2",
+ "socket2 0.5.7",
"windows-sys 0.52.0",
]
@@ -2763,18 +4128,45 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
+[[package]]
+name = "unicode-bidi"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
+
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+[[package]]
+name = "unicode-normalization"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
[[package]]
name = "universal-hash"
version = "0.5.1"
@@ -2787,9 +4179,9 @@ dependencies = [
[[package]]
name = "url"
-version = "2.5.3"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -2835,12 +4227,30 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+[[package]]
+name = "value-bag"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "waker-fn"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
+
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -2857,6 +4267,12 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
@@ -2883,6 +4299,18 @@ dependencies = [
"wasm-bindgen-shared",
]
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
@@ -2912,6 +4340,26 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+[[package]]
+name = "web-sys"
+version = "0.3.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "whoami"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -3121,6 +4569,12 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
[[package]]
name = "yoke"
version = "0.7.4"
@@ -3187,6 +4641,12 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
[[package]]
name = "zerovec"
version = "0.10.4"
diff --git a/Cargo.toml b/Cargo.toml
index 35969913..e37f712e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ members = [
# Packages
"packages/pagetop-aliner",
"packages/pagetop-bootsier",
+ "packages/pagetop-seaorm",
# App
"drust",
diff --git a/packages/pagetop-seaorm/CREDITS.md b/packages/pagetop-seaorm/CREDITS.md
new file mode 100644
index 00000000..d5dc7f83
--- /dev/null
+++ b/packages/pagetop-seaorm/CREDITS.md
@@ -0,0 +1,67 @@
+# 🔃 Dependencies
+
+PageTop is developed in the [Rust programming language](https://www.rust-lang.org/) and stands on
+the shoulders of true giants, using some of the most stable and renowned libraries (*crates*) from
+the [Rust ecosystem](https://lib.rs), such as:
+
+* [Actix Web](https://actix.rs/) for web services and server management.
+* [Tracing](https://github.com/tokio-rs/tracing) for the diagnostic system and structured logging.
+* [Fluent templates](https://github.com/XAMPPRocky/fluent-templates) that incorporate
+ [Fluent](https://projectfluent.org/) for project internationalization.
+* [SeaORM](https://www.sea-ql.org/SeaORM/) which employs [SQLx](https://docs.rs/sqlx/latest/sqlx/)
+ for database access and modeling.
+* Among others, which you can review in the PageTop
+ [`Cargo.toml`](https://github.com/manuelcillero/pagetop/blob/main/Cargo.toml) file.
+
+
+# ⌨️ Code
+
+PageTop integrates code from various renowned crates to enhance functionality:
+
+* [**Config (v0.11.0)**](https://github.com/mehcode/config-rs/tree/0.11.0): Includes code from
+ [config-rs](https://crates.io/crates/config) by [Ryan Leckey](https://crates.io/users/mehcode),
+ chosen for its advantages in reading configuration settings and delegating assignment to safe
+ types, tailored to the specific needs of each package, theme, or application.
+
+* [**Maud (v0.25.0)**](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud): An adapted version
+ of the excellent [maud](https://crates.io/crates/maud) crate by
+ [Chris Wong](https://crates.io/users/lambda-fairy) is incorporated to leverage its functionalities without requiring a reference to `maud` in the `Cargo.toml` files.
+
+* **SmartDefault (v0.7.1)**: Embedded [SmartDefault](https://crates.io/crates/smart_default) by
+ [Jane Doe](https://crates.io/users/jane-doe) as `AutoDefault`to simplify the documentation of
+ Default implementations and also removes the need to explicitly list `smart_default` in the
+ `Cargo.toml` files.
+
+* **Database Operations**: PageTop employs [SQLx](https://github.com/launchbadge/sqlx) and
+ [SeaQuery](https://github.com/SeaQL/sea-query), complemented by a custom version of
+ [SeaORM Migration](https://github.com/SeaQL/sea-orm/tree/master/sea-orm-migration) (version
+ [0.12.8](https://github.com/SeaQL/sea-orm/tree/0.12.8/sea-orm-migration/src)). This modification
+ ensures migration processes are confined to specific packages, enhancing modularity and
+ maintainability.
+
+
+# 🗚 FIGfonts
+
+PageTop uses the [figlet-rs](https://crates.io/crates/figlet-rs) package by *yuanbohan* to display a
+presentation banner in the terminal with the application's name using
+[FIGlet](http://www.figlet.org) characters. The fonts included in `src/app` are:
+
+* [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) by *Glenn Chappell*
+* [small.flf](http://www.figlet.org/fontdb_example.cgi?font=small.flf) by *Glenn Chappell* (default)
+* [speed.flf](http://www.figlet.org/fontdb_example.cgi?font=speed.flf) by *Claude Martins*
+* [starwars.flf](http://www.figlet.org/fontdb_example.cgi?font=starwars.flf) by *Ryan Youck*
+
+
+# 📰 Templates
+
+* The default welcome homepage design is based on the
+ [Zinc](https://themewagon.com/themes/free-bootstrap-5-html5-business-website-template-zinc)
+ template created by [inovatik](https://inovatik.com/) and distributed by
+ [ThemeWagon](https://themewagon.com).
+
+
+# 🎨 Icon
+
+"The creature" smiling is a fun creation by [Webalys](https://www.iconfinder.com/webalys). It can be
+found in their [Nasty Icons](https://www.iconfinder.com/iconsets/nasty) collection available on
+[ICONFINDER](https://www.iconfinder.com).
diff --git a/packages/pagetop-seaorm/Cargo.toml b/packages/pagetop-seaorm/Cargo.toml
new file mode 100644
index 00000000..9ee0a2f3
--- /dev/null
+++ b/packages/pagetop-seaorm/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "pagetop-seaorm"
+version = "0.0.1"
+edition = "2021"
+
+description = """\
+ Integrate SeaORM as the database framework for PageTop applications.\
+"""
+categories = ["web-programming", "database"]
+keywords = ["pagetop", "database", "sql", "orm"]
+
+homepage = { workspace = true }
+repository = { workspace = true }
+authors = { workspace = true }
+license = { workspace = true }
+
+[dependencies]
+pagetop.workspace = true
+
+async-trait = "0.1.83"
+futures = "0.3.31"
+serde.workspace = true
+static-files.workspace = true
+url = "2.5.4"
+
+[dependencies.sea-orm]
+version = "1.1.1"
+features = [
+ "debug-print", "macros", "runtime-async-std-native-tls",
+ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite",
+]
+default-features = false
+
+[dependencies.sea-schema]
+version = "0.16.0"
diff --git a/packages/pagetop-seaorm/README.md b/packages/pagetop-seaorm/README.md
new file mode 100644
index 00000000..14523297
--- /dev/null
+++ b/packages/pagetop-seaorm/README.md
@@ -0,0 +1,127 @@
+
+
+
+
+
PageTop
+
+
An opinionated web framework to build modular Server-Side Rendering web solutions.
+
+[](#-license)
+[](https://docs.rs/pagetop)
+[](https://crates.io/crates/pagetop)
+[](https://crates.io/crates/pagetop)
+
+
+
+## Overview
+
+The PageTop core API provides a comprehensive toolkit for extending its functionalities to specific
+requirements and application scenarios through actions, components, packages, and themes:
+
+ * **Actions** serve as a mechanism to customize PageTop's internal behavior by intercepting its
+ execution flow.
+ * **Components** encapsulate HTML, CSS, and JavaScript into functional, configurable, and
+ well-defined units.
+ * **Packages** extend or customize existing functionality by interacting with PageTop APIs or
+ third-party package APIs.
+ * **Themes** enable developers to alter the appearance of pages and components without affecting
+ their functionality.
+
+
+# ⚡️ Quick start
+
+```rust
+use pagetop::prelude::*;
+
+struct HelloWorld;
+
+impl PackageTrait for HelloWorld {
+ fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
+ scfg.route("/", service::web::get().to(hello_world));
+ }
+}
+
+async fn hello_world(request: HttpRequest) -> ResultPage {
+ Page::new(request)
+ .with_component(Html::with(html! { h1 { "Hello World!" } }))
+ .render()
+}
+
+#[pagetop::main]
+async fn main() -> std::io::Result<()> {
+ Application::prepare(&HelloWorld).run()?.await
+}
+```
+
+This program features a `HelloWorld` package, providing a service that serves a greeting web page
+accessible via `http://localhost:8088` under default settings.
+
+
+# 📂 Repository Structure
+
+This repository is organized into a workspace that includes several subprojects, each serving a
+distinct role within the PageTop ecosystem:
+
+## Application
+
+* [drust](https://github.com/manuelcillero/pagetop/tree/latest/drust):
+ A simple Content Management System (CMS) built on PageTop, which enables the creation, editing,
+ and maintenance of dynamic, fast, and modular websites. It uses the following essential packages
+ to provide standard CMS functionalities.
+
+## Helpers
+
+* [pagetop-macros](https://github.com/manuelcillero/pagetop/tree/latest/helpers/pagetop-macros):
+ A collection of procedural macros that enhance the development experience within PageTop.
+
+* [pagetop-build](https://github.com/manuelcillero/pagetop/tree/latest/helpers/pagetop-build):
+ Simplifies the process of embedding resources directly into binary files for PageTop applications.
+
+## Packages
+
+* [pagetop-user](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-user):
+ Facilitates user management, including roles, permissions, and session handling, for applications
+ built on PageTop.
+
+* [pagetop-admin](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-admin):
+ Provides a unified interface for administrators to configure and manage package settings.
+
+* [pagetop-node](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-node):
+ Enables the creation and customization of content types, enhancing website content management.
+
+## Themes
+
+* [pagetop-bootsier](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-bootsier):
+ Utilizes the *[Bootstrap](https://getbootstrap.com/)* framework to offer versatile page layouts
+ and component stylings.
+
+* [pagetop-bulmix](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-bulmix):
+ Utilizes the *[Bulma](https://bulma.io/)* framework for sleek, responsive design elements.
+
+
+# 🚧 Warning
+
+**PageTop** framework is currently in active development. The API is unstable and subject to
+frequent changes. Production use is not recommended until version **0.1.0**.
+
+
+# 📜 License
+
+PageTop is free, open source and permissively licensed! Except where noted (below and/or in
+individual files), all code in this project is dual-licensed under either:
+
+ * MIT License
+ ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
+
+ * Apache License, Version 2.0,
+ ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
+
+at your option. This means you can select the license you prefer! This dual-licensing approach is
+the de-facto standard in the Rust ecosystem.
+
+
+# ✨ Contributions
+
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the
+work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/packages/pagetop-seaorm/src/config.rs b/packages/pagetop-seaorm/src/config.rs
new file mode 100644
index 00000000..b0bef472
--- /dev/null
+++ b/packages/pagetop-seaorm/src/config.rs
@@ -0,0 +1,70 @@
+//! Configuration settings for SeaORM PageTop package.
+//!
+//! Example:
+//!
+//! ```toml
+//! [database]
+//! db_type = "mysql"
+//! db_name = "db"
+//! db_user = "user"
+//! db_pass = "password"
+//! db_host = "localhost"
+//! db_port = 3306
+//! max_pool_size = 5
+//! ```
+//!
+//! Usage:
+//!
+//! ```rust
+//! use pagetop_seaorm::config;
+//!
+//! assert_eq!(config::SETTINGS.database.db_host, "localhost");
+//! ```
+//! See [`pagetop::config`](pagetop::config) to learn how **PageTop** read configuration files and
+//! use settings.
+
+use pagetop::prelude::*;
+
+use serde::Deserialize;
+
+include_config!(SETTINGS: Settings => [
+ // [database]
+ "database.db_type" => "",
+ "database.db_name" => "",
+ "database.db_user" => "",
+ "database.db_pass" => "",
+ "database.db_host" => "localhost",
+ "database.db_port" => 0,
+ "database.max_pool_size" => 5,
+]);
+
+#[derive(Debug, Deserialize)]
+/// Type for HighlightJS configuration settings, section [`[hljs]`](Hljs) (used by [`SETTINGS`]).
+pub struct Settings {
+ pub database: Database,
+}
+#[derive(Debug, Deserialize)]
+/// Struct for section `[database]` of [`Settings`] type.
+pub struct Database {
+ /// Tipo de base de datos: *"mysql"*, *"postgres"* ó *"sqlite"*.
+ /// Por defecto: *""*.
+ pub db_type: String,
+ /// Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos.
+ /// Por defecto: *""*.
+ pub db_name: String,
+ /// Usuario de conexión a la base de datos (para mysql/postgres).
+ /// Por defecto: *""*.
+ pub db_user: String,
+ /// Contraseña para la conexión a la base de datos (para mysql/postgres).
+ /// Por defecto: *""*.
+ pub db_pass: String,
+ /// Servidor de conexión a la base de datos (para mysql/postgres).
+ /// Por defecto: *"localhost"*.
+ pub db_host: String,
+ /// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres).
+ /// Por defecto: *0*.
+ pub db_port: u16,
+ /// Número máximo de conexiones habilitadas.
+ /// Por defecto: *5*.
+ pub max_pool_size: u32,
+}
diff --git a/packages/pagetop-seaorm/src/db.rs b/packages/pagetop-seaorm/src/db.rs
new file mode 100644
index 00000000..cece9a8f
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db.rs
@@ -0,0 +1,132 @@
+use pagetop::trace;
+use pagetop::util::TypeInfo;
+
+pub use url::Url as DbUri;
+
+pub use sea_orm::error::{DbErr, RuntimeErr};
+pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult};
+
+use sea_orm::{ConnectionTrait, DatabaseBackend, Statement};
+
+mod dbconn;
+pub(crate) use dbconn::{run_now, DBCONN};
+
+// The migration module is a customized version of the sea_orm_migration module (v1.0.0)
+// https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration to avoid errors caused by the
+// package paradigm of PageTop. Files integrated from original:
+//
+// lib.rs => db/migration.rs . . . . . . . . . . . . . . (excluding some modules and exports)
+// connection.rs => db/migration/connection.rs . . . . . . . . . . . . . . (full integration)
+// manager.rs => db/migration/manager.rs . . . . . . . . . . . . . . . . . (full integration)
+// migrator.rs => db/migration/migrator.rs . . . . . . . . . . . .(omitting error management)
+// prelude.rs => db/migration/prelude.rs . . . . . . . . . . . . . . . . . . . (avoiding CLI)
+// seaql_migrations.rs => db/migration/seaql_migrations.rs . . . . . . . . (full integration)
+//
+mod migration;
+pub use migration::prelude::*;
+pub use migration::schema::*;
+
+pub async fn query(stmt: &mut Q) -> Result, DbErr> {
+ let dbconn = &*DBCONN;
+ let dbbackend = dbconn.get_database_backend();
+ dbconn
+ .query_all(Statement::from_string(
+ dbbackend,
+ match dbbackend {
+ DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
+ DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
+ DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
+ },
+ ))
+ .await
+}
+
+pub async fn exec(stmt: &mut Q) -> Result, DbErr> {
+ let dbconn = &*DBCONN;
+ let dbbackend = dbconn.get_database_backend();
+ dbconn
+ .query_one(Statement::from_string(
+ dbbackend,
+ match dbbackend {
+ DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
+ DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
+ DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
+ },
+ ))
+ .await
+}
+
+pub async fn exec_raw(stmt: String) -> Result {
+ let dbconn = &*DBCONN;
+ let dbbackend = dbconn.get_database_backend();
+ dbconn
+ .execute(Statement::from_string(dbbackend, stmt))
+ .await
+}
+
+pub trait MigratorBase {
+ fn run_up();
+
+ fn run_down();
+}
+
+#[rustfmt::skip]
+impl MigratorBase for M {
+ fn run_up() {
+ if let Err(e) = run_now(Self::up(SchemaManagerConnection::Connection(&DBCONN), None)) {
+ trace::error!("Migration upgrade failed ({})", e);
+ };
+ }
+
+ fn run_down() {
+ if let Err(e) = run_now(Self::down(SchemaManagerConnection::Connection(&DBCONN), None)) {
+ trace::error!("Migration downgrade failed ({})", e);
+ };
+ }
+}
+
+impl MigrationName for M {
+ fn name(&self) -> &str {
+ TypeInfo::NameTo(-2).of::()
+ }
+}
+
+pub type MigrationItem = Box;
+
+#[macro_export]
+macro_rules! install_migrations {
+ ( $($migration_module:ident),+ $(,)? ) => {{
+ use $crate::db::{MigrationItem, MigratorBase, MigratorTrait};
+
+ struct Migrator;
+ impl MigratorTrait for Migrator {
+ fn migrations() -> Vec {
+ let mut m = Vec::::new();
+ $(
+ m.push(Box::new(migration::$migration_module::Migration));
+ )*
+ m
+ }
+ }
+ Migrator::run_up();
+ }};
+}
+
+#[macro_export]
+macro_rules! uninstall_migrations {
+ ( $($migration_module:ident),+ $(,)? ) => {{
+ use $crate::db::{MigrationItem, MigratorBase, MigratorTrait};
+
+ struct Migrator;
+ impl MigratorTrait for Migrator {
+ fn migrations() -> Vec {
+ let mut m = Vec::::new();
+ $(
+ m.push(Box::new(migration::$migration_module::Migration));
+ )*
+ m
+ }
+ }
+ Migrator::run_down();
+ }};
+}
diff --git a/packages/pagetop-seaorm/src/db/dbconn.rs b/packages/pagetop-seaorm/src/db/dbconn.rs
new file mode 100644
index 00000000..bd227956
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/dbconn.rs
@@ -0,0 +1,69 @@
+use pagetop::trace;
+
+use crate::config;
+use crate::db::{DbConn, DbUri};
+
+use std::sync::LazyLock;
+
+use sea_orm::{ConnectOptions, Database};
+
+pub use futures::executor::block_on as run_now;
+
+pub static DBCONN: LazyLock = LazyLock::new(|| {
+ trace::info!(
+ "Connecting to database \"{}\" using a pool of {} connections",
+ &config::SETTINGS.database.db_name,
+ &config::SETTINGS.database.max_pool_size
+ );
+
+ let db_uri = match config::SETTINGS.database.db_type.as_str() {
+ "mysql" | "postgres" => {
+ let mut tmp_uri = DbUri::parse(
+ format!(
+ "{}://{}/{}",
+ &config::SETTINGS.database.db_type,
+ &config::SETTINGS.database.db_host,
+ &config::SETTINGS.database.db_name
+ )
+ .as_str(),
+ )
+ .unwrap();
+ tmp_uri
+ .set_username(config::SETTINGS.database.db_user.as_str())
+ .unwrap();
+ // https://github.com/launchbadge/sqlx/issues/1624
+ tmp_uri
+ .set_password(Some(config::SETTINGS.database.db_pass.as_str()))
+ .unwrap();
+ if config::SETTINGS.database.db_port != 0 {
+ tmp_uri
+ .set_port(Some(config::SETTINGS.database.db_port))
+ .unwrap();
+ }
+ tmp_uri
+ }
+ "sqlite" => DbUri::parse(
+ format!(
+ "{}://{}",
+ &config::SETTINGS.database.db_type,
+ &config::SETTINGS.database.db_name
+ )
+ .as_str(),
+ )
+ .unwrap(),
+ _ => {
+ trace::error!(
+ "Unrecognized database type \"{}\"",
+ &config::SETTINGS.database.db_type
+ );
+ DbUri::parse("").unwrap()
+ }
+ };
+
+ run_now(Database::connect::({
+ let mut db_opt = ConnectOptions::new(db_uri.to_string());
+ db_opt.max_connections(config::SETTINGS.database.max_pool_size);
+ db_opt
+ }))
+ .unwrap_or_else(|_| panic!("Failed to connect to database"))
+});
diff --git a/packages/pagetop-seaorm/src/db/migration.rs b/packages/pagetop-seaorm/src/db/migration.rs
new file mode 100644
index 00000000..29314bf6
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration.rs
@@ -0,0 +1,33 @@
+//pub mod cli;
+pub mod connection;
+pub mod manager;
+pub mod migrator;
+pub mod prelude;
+pub mod schema;
+pub mod seaql_migrations;
+//pub mod util;
+
+pub use connection::*;
+pub use manager::*;
+//pub use migrator::*;
+
+pub use async_trait;
+//pub use sea_orm;
+//pub use sea_orm::sea_query;
+use sea_orm::DbErr;
+
+pub trait MigrationName {
+ fn name(&self) -> &str;
+}
+
+/// The migration definition
+#[async_trait::async_trait]
+pub trait MigrationTrait: MigrationName + Send + Sync {
+ /// Define actions to perform when applying the migration
+ async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>;
+
+ /// Define actions to perform when rolling back the migration
+ async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+ Err(DbErr::Migration("We Don't Do That Here".to_owned()))
+ }
+}
diff --git a/packages/pagetop-seaorm/src/db/migration/connection.rs b/packages/pagetop-seaorm/src/db/migration/connection.rs
new file mode 100644
index 00000000..116185e4
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/connection.rs
@@ -0,0 +1,148 @@
+use futures::Future;
+use sea_orm::{
+ AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr,
+ ExecResult, IsolationLevel, QueryResult, Statement, TransactionError, TransactionTrait,
+};
+use std::pin::Pin;
+
+pub enum SchemaManagerConnection<'c> {
+ Connection(&'c DatabaseConnection),
+ Transaction(&'c DatabaseTransaction),
+}
+
+#[async_trait::async_trait]
+impl<'c> ConnectionTrait for SchemaManagerConnection<'c> {
+ fn get_database_backend(&self) -> DbBackend {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.get_database_backend(),
+ SchemaManagerConnection::Transaction(trans) => trans.get_database_backend(),
+ }
+ }
+
+ async fn execute(&self, stmt: Statement) -> Result {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.execute(stmt).await,
+ SchemaManagerConnection::Transaction(trans) => trans.execute(stmt).await,
+ }
+ }
+
+ async fn execute_unprepared(&self, sql: &str) -> Result {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.execute_unprepared(sql).await,
+ SchemaManagerConnection::Transaction(trans) => trans.execute_unprepared(sql).await,
+ }
+ }
+
+ async fn query_one(&self, stmt: Statement) -> Result, DbErr> {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.query_one(stmt).await,
+ SchemaManagerConnection::Transaction(trans) => trans.query_one(stmt).await,
+ }
+ }
+
+ async fn query_all(&self, stmt: Statement) -> Result, DbErr> {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.query_all(stmt).await,
+ SchemaManagerConnection::Transaction(trans) => trans.query_all(stmt).await,
+ }
+ }
+
+ fn is_mock_connection(&self) -> bool {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.is_mock_connection(),
+ SchemaManagerConnection::Transaction(trans) => trans.is_mock_connection(),
+ }
+ }
+}
+
+#[async_trait::async_trait]
+impl<'c> TransactionTrait for SchemaManagerConnection<'c> {
+ async fn begin(&self) -> Result {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.begin().await,
+ SchemaManagerConnection::Transaction(trans) => trans.begin().await,
+ }
+ }
+
+ async fn begin_with_config(
+ &self,
+ isolation_level: Option,
+ access_mode: Option,
+ ) -> Result {
+ match self {
+ SchemaManagerConnection::Connection(conn) => {
+ conn.begin_with_config(isolation_level, access_mode).await
+ }
+ SchemaManagerConnection::Transaction(trans) => {
+ trans.begin_with_config(isolation_level, access_mode).await
+ }
+ }
+ }
+
+ async fn transaction(&self, callback: F) -> Result>
+ where
+ F: for<'a> FnOnce(
+ &'a DatabaseTransaction,
+ ) -> Pin> + Send + 'a>>
+ + Send,
+ T: Send,
+ E: std::error::Error + Send,
+ {
+ match self {
+ SchemaManagerConnection::Connection(conn) => conn.transaction(callback).await,
+ SchemaManagerConnection::Transaction(trans) => trans.transaction(callback).await,
+ }
+ }
+
+ async fn transaction_with_config(
+ &self,
+ callback: F,
+ isolation_level: Option,
+ access_mode: Option,
+ ) -> Result>
+ where
+ F: for<'a> FnOnce(
+ &'a DatabaseTransaction,
+ ) -> Pin> + Send + 'a>>
+ + Send,
+ T: Send,
+ E: std::error::Error + Send,
+ {
+ match self {
+ SchemaManagerConnection::Connection(conn) => {
+ conn.transaction_with_config(callback, isolation_level, access_mode)
+ .await
+ }
+ SchemaManagerConnection::Transaction(trans) => {
+ trans
+ .transaction_with_config(callback, isolation_level, access_mode)
+ .await
+ }
+ }
+ }
+}
+
+pub trait IntoSchemaManagerConnection<'c>: Send
+where
+ Self: 'c,
+{
+ fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c>;
+}
+
+impl<'c> IntoSchemaManagerConnection<'c> for SchemaManagerConnection<'c> {
+ fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> {
+ self
+ }
+}
+
+impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseConnection {
+ fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> {
+ SchemaManagerConnection::Connection(self)
+ }
+}
+
+impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseTransaction {
+ fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> {
+ SchemaManagerConnection::Transaction(self)
+ }
+}
diff --git a/packages/pagetop-seaorm/src/db/migration/manager.rs b/packages/pagetop-seaorm/src/db/migration/manager.rs
new file mode 100644
index 00000000..d1cc3b6a
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/manager.rs
@@ -0,0 +1,167 @@
+use super::{IntoSchemaManagerConnection, SchemaManagerConnection};
+use sea_orm::sea_query::{
+ extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement},
+ ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement,
+ TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement,
+ TableTruncateStatement,
+};
+use sea_orm::{ConnectionTrait, DbBackend, DbErr, StatementBuilder};
+use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite};
+
+/// Helper struct for writing migration scripts in migration file
+pub struct SchemaManager<'c> {
+ conn: SchemaManagerConnection<'c>,
+}
+
+impl<'c> SchemaManager<'c> {
+ pub fn new(conn: T) -> Self
+ where
+ T: IntoSchemaManagerConnection<'c>,
+ {
+ Self {
+ conn: conn.into_schema_manager_connection(),
+ }
+ }
+
+ pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr>
+ where
+ S: StatementBuilder,
+ {
+ let builder = self.conn.get_database_backend();
+ self.conn.execute(builder.build(&stmt)).await.map(|_| ())
+ }
+
+ pub fn get_database_backend(&self) -> DbBackend {
+ self.conn.get_database_backend()
+ }
+
+ pub fn get_connection(&self) -> &SchemaManagerConnection<'c> {
+ &self.conn
+ }
+}
+
+/// Schema Creation
+impl<'c> SchemaManager<'c> {
+ pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+}
+
+/// Schema Mutation
+impl<'c> SchemaManager<'c> {
+ pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+
+ pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> {
+ self.exec_stmt(stmt).await
+ }
+}
+
+/// Schema Inspection.
+impl<'c> SchemaManager<'c> {
+ pub async fn has_table(&self, table: T) -> Result
+ where
+ T: AsRef,
+ {
+ has_table(&self.conn, table).await
+ }
+
+ pub async fn has_column(&self, table: T, column: C) -> Result
+ where
+ T: AsRef,
+ C: AsRef,
+ {
+ let stmt = match self.conn.get_database_backend() {
+ DbBackend::MySql => MySql.has_column(table, column),
+ DbBackend::Postgres => Postgres.has_column(table, column),
+ DbBackend::Sqlite => Sqlite.has_column(table, column),
+ };
+
+ let builder = self.conn.get_database_backend();
+ let res = self
+ .conn
+ .query_one(builder.build(&stmt))
+ .await?
+ .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?;
+
+ res.try_get("", "has_column")
+ }
+
+ pub async fn has_index(&self, table: T, index: I) -> Result
+ where
+ T: AsRef,
+ I: AsRef,
+ {
+ let stmt = match self.conn.get_database_backend() {
+ DbBackend::MySql => MySql.has_index(table, index),
+ DbBackend::Postgres => Postgres.has_index(table, index),
+ DbBackend::Sqlite => Sqlite.has_index(table, index),
+ };
+
+ let builder = self.conn.get_database_backend();
+ let res = self
+ .conn
+ .query_one(builder.build(&stmt))
+ .await?
+ .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?;
+
+ res.try_get("", "has_index")
+ }
+}
+
+pub(crate) async fn has_table(conn: &C, table: T) -> Result
+where
+ C: ConnectionTrait,
+ T: AsRef,
+{
+ let stmt = match conn.get_database_backend() {
+ DbBackend::MySql => MySql.has_table(table),
+ DbBackend::Postgres => Postgres.has_table(table),
+ DbBackend::Sqlite => Sqlite.has_table(table),
+ };
+
+ let builder = conn.get_database_backend();
+ let res = conn
+ .query_one(builder.build(&stmt))
+ .await?
+ .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?;
+
+ res.try_get("", "has_table")
+}
diff --git a/packages/pagetop-seaorm/src/db/migration/migrator.rs b/packages/pagetop-seaorm/src/db/migration/migrator.rs
new file mode 100644
index 00000000..06611412
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/migrator.rs
@@ -0,0 +1,593 @@
+use futures::Future;
+use std::collections::HashSet;
+use std::fmt::Display;
+use std::pin::Pin;
+use std::time::SystemTime;
+
+use pagetop::trace::info;
+
+use sea_orm::sea_query::{
+ self, extension::postgres::Type, Alias, Expr, ForeignKey, IntoIden, JoinType, Order, Query,
+ SelectStatement, SimpleExpr, Table,
+};
+use sea_orm::{
+ ActiveModelTrait, ActiveValue, Condition, ConnectionTrait, DbBackend, DbErr, DeriveIden,
+ DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement,
+ TransactionTrait,
+};
+use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite};
+
+use super::{seaql_migrations, IntoSchemaManagerConnection, MigrationTrait, SchemaManager};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+/// Status of migration
+pub enum MigrationStatus {
+ /// Not yet applied
+ Pending,
+ /// Applied
+ Applied,
+}
+
+impl Display for MigrationStatus {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let status = match self {
+ MigrationStatus::Pending => "Pending",
+ MigrationStatus::Applied => "Applied",
+ };
+ write!(f, "{status}")
+ }
+}
+
+pub struct Migration {
+ migration: Box,
+ status: MigrationStatus,
+}
+
+impl Migration {
+ /// Get migration name from MigrationName trait implementation
+ pub fn name(&self) -> &str {
+ self.migration.name()
+ }
+
+ /// Get migration status
+ pub fn status(&self) -> MigrationStatus {
+ self.status
+ }
+}
+
+/// Performing migrations on a database
+#[async_trait::async_trait]
+pub trait MigratorTrait: Send {
+ /// Vector of migrations in time sequence
+ fn migrations() -> Vec>;
+
+ /// Name of the migration table, it is `seaql_migrations` by default
+ fn migration_table_name() -> DynIden {
+ seaql_migrations::Entity.into_iden()
+ }
+
+ /// Get list of migrations wrapped in `Migration` struct
+ fn get_migration_files() -> Vec {
+ Self::migrations()
+ .into_iter()
+ .map(|migration| Migration {
+ migration,
+ status: MigrationStatus::Pending,
+ })
+ .collect()
+ }
+
+ /// Get list of applied migrations from database
+ async fn get_migration_models(db: &C) -> Result, DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ Self::install(db).await?;
+ let stmt = Query::select()
+ .table_name(Self::migration_table_name())
+ .columns(seaql_migrations::Column::iter().map(IntoIden::into_iden))
+ .order_by(seaql_migrations::Column::Version, Order::Asc)
+ .to_owned();
+ let builder = db.get_database_backend();
+ seaql_migrations::Model::find_by_statement(builder.build(&stmt))
+ .all(db)
+ .await
+ }
+
+ /// Get list of migrations with status
+ async fn get_migration_with_status(db: &C) -> Result, DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ Self::install(db).await?;
+ let mut migration_files = Self::get_migration_files();
+ let migration_models = Self::get_migration_models(db).await?;
+
+ let migration_in_db: HashSet = migration_models
+ .into_iter()
+ .map(|model| model.version)
+ .collect();
+ let migration_in_fs: HashSet = migration_files
+ .iter()
+ .map(|file| file.migration.name().to_string())
+ .collect();
+
+ let pending_migrations = &migration_in_fs - &migration_in_db;
+ for migration_file in migration_files.iter_mut() {
+ if !pending_migrations.contains(migration_file.migration.name()) {
+ migration_file.status = MigrationStatus::Applied;
+ }
+ }
+ /*
+ let missing_migrations_in_fs = &migration_in_db - &migration_in_fs;
+ let errors: Vec = missing_migrations_in_fs
+ .iter()
+ .map(|missing_migration| {
+ format!("Migration file of version '{missing_migration}' is missing, this migration has been applied but its file is missing")
+ }).collect();
+
+ if !errors.is_empty() {
+ Err(DbErr::Custom(errors.join("\n")))
+ } else { */
+ Ok(migration_files)
+ /* } */
+ }
+
+ /// Get list of pending migrations
+ async fn get_pending_migrations(db: &C) -> Result, DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ Self::install(db).await?;
+ Ok(Self::get_migration_with_status(db)
+ .await?
+ .into_iter()
+ .filter(|file| file.status == MigrationStatus::Pending)
+ .collect())
+ }
+
+ /// Get list of applied migrations
+ async fn get_applied_migrations(db: &C) -> Result, DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ Self::install(db).await?;
+ Ok(Self::get_migration_with_status(db)
+ .await?
+ .into_iter()
+ .filter(|file| file.status == MigrationStatus::Applied)
+ .collect())
+ }
+
+ /// Create migration table `seaql_migrations` in the database
+ async fn install(db: &C) -> Result<(), DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ let builder = db.get_database_backend();
+ let table_name = Self::migration_table_name();
+ let schema = Schema::new(builder);
+ let mut stmt = schema
+ .create_table_from_entity(seaql_migrations::Entity)
+ .table_name(table_name);
+ stmt.if_not_exists();
+ db.execute(builder.build(&stmt)).await.map(|_| ())
+ }
+
+ /// Check the status of all migrations
+ async fn status(db: &C) -> Result<(), DbErr>
+ where
+ C: ConnectionTrait,
+ {
+ Self::install(db).await?;
+
+ info!("Checking migration status");
+
+ for Migration { migration, status } in Self::get_migration_with_status(db).await? {
+ info!("Migration '{}'... {}", migration.name(), status);
+ }
+
+ Ok(())
+ }
+
+ /// Drop all tables from the database, then reapply all migrations
+ async fn fresh<'c, C>(db: C) -> Result<(), DbErr>
+ where
+ C: IntoSchemaManagerConnection<'c>,
+ {
+ exec_with_connection::<'_, _, _>(db, move |manager| {
+ Box::pin(async move { exec_fresh::(manager).await })
+ })
+ .await
+ }
+
+ /// Rollback all applied migrations, then reapply all migrations
+ async fn refresh<'c, C>(db: C) -> Result<(), DbErr>
+ where
+ C: IntoSchemaManagerConnection<'c>,
+ {
+ exec_with_connection::<'_, _, _>(db, move |manager| {
+ Box::pin(async move {
+ exec_down::(manager, None).await?;
+ exec_up::(manager, None).await
+ })
+ })
+ .await
+ }
+
+ /// Rollback all applied migrations
+ async fn reset<'c, C>(db: C) -> Result<(), DbErr>
+ where
+ C: IntoSchemaManagerConnection<'c>,
+ {
+ exec_with_connection::<'_, _, _>(db, move |manager| {
+ Box::pin(async move { exec_down::(manager, None).await })
+ })
+ .await
+ }
+
+ /// Apply pending migrations
+ async fn up<'c, C>(db: C, steps: Option) -> Result<(), DbErr>
+ where
+ C: IntoSchemaManagerConnection<'c>,
+ {
+ exec_with_connection::<'_, _, _>(db, move |manager| {
+ Box::pin(async move { exec_up::(manager, steps).await })
+ })
+ .await
+ }
+
+ /// Rollback applied migrations
+ async fn down<'c, C>(db: C, steps: Option) -> Result<(), DbErr>
+ where
+ C: IntoSchemaManagerConnection<'c>,
+ {
+ exec_with_connection::<'_, _, _>(db, move |manager| {
+ Box::pin(async move { exec_down::(manager, steps).await })
+ })
+ .await
+ }
+}
+
+async fn exec_with_connection<'c, C, F>(db: C, f: F) -> Result<(), DbErr>
+where
+ C: IntoSchemaManagerConnection<'c>,
+ F: for<'b> Fn(
+ &'b SchemaManager<'_>,
+ ) -> Pin> + Send + 'b>>,
+{
+ let db = db.into_schema_manager_connection();
+
+ match db.get_database_backend() {
+ DbBackend::Postgres => {
+ let transaction = db.begin().await?;
+ let manager = SchemaManager::new(&transaction);
+ f(&manager).await?;
+ transaction.commit().await
+ }
+ DbBackend::MySql | DbBackend::Sqlite => {
+ let manager = SchemaManager::new(db);
+ f(&manager).await
+ }
+ }
+}
+
+async fn exec_fresh(manager: &SchemaManager<'_>) -> Result<(), DbErr>
+where
+ M: MigratorTrait + ?Sized,
+{
+ let db = manager.get_connection();
+
+ M::install(db).await?;
+ let db_backend = db.get_database_backend();
+
+ // Temporarily disable the foreign key check
+ if db_backend == DbBackend::Sqlite {
+ info!("Disabling foreign key check");
+ db.execute(Statement::from_string(
+ db_backend,
+ "PRAGMA foreign_keys = OFF".to_owned(),
+ ))
+ .await?;
+ info!("Foreign key check disabled");
+ }
+
+ // Drop all foreign keys
+ if db_backend == DbBackend::MySql {
+ info!("Dropping all foreign keys");
+ let stmt = query_mysql_foreign_keys(db);
+ let rows = db.query_all(db_backend.build(&stmt)).await?;
+ for row in rows.into_iter() {
+ let constraint_name: String = row.try_get("", "CONSTRAINT_NAME")?;
+ let table_name: String = row.try_get("", "TABLE_NAME")?;
+ info!(
+ "Dropping foreign key '{}' from table '{}'",
+ constraint_name, table_name
+ );
+ let mut stmt = ForeignKey::drop();
+ stmt.table(Alias::new(table_name.as_str()))
+ .name(constraint_name.as_str());
+ db.execute(db_backend.build(&stmt)).await?;
+ info!("Foreign key '{}' has been dropped", constraint_name);
+ }
+ info!("All foreign keys dropped");
+ }
+
+ // Drop all tables
+ let stmt = query_tables(db).await;
+ let rows = db.query_all(db_backend.build(&stmt)).await?;
+ for row in rows.into_iter() {
+ let table_name: String = row.try_get("", "table_name")?;
+ info!("Dropping table '{}'", table_name);
+ let mut stmt = Table::drop();
+ stmt.table(Alias::new(table_name.as_str()))
+ .if_exists()
+ .cascade();
+ db.execute(db_backend.build(&stmt)).await?;
+ info!("Table '{}' has been dropped", table_name);
+ }
+
+ // Drop all types
+ if db_backend == DbBackend::Postgres {
+ info!("Dropping all types");
+ let stmt = query_pg_types(db);
+ let rows = db.query_all(db_backend.build(&stmt)).await?;
+ for row in rows {
+ let type_name: String = row.try_get("", "typname")?;
+ info!("Dropping type '{}'", type_name);
+ let mut stmt = Type::drop();
+ stmt.name(Alias::new(&type_name));
+ db.execute(db_backend.build(&stmt)).await?;
+ info!("Type '{}' has been dropped", type_name);
+ }
+ }
+
+ // Restore the foreign key check
+ if db_backend == DbBackend::Sqlite {
+ info!("Restoring foreign key check");
+ db.execute(Statement::from_string(
+ db_backend,
+ "PRAGMA foreign_keys = ON".to_owned(),
+ ))
+ .await?;
+ info!("Foreign key check restored");
+ }
+
+ // Reapply all migrations
+ exec_up::(manager, None).await
+}
+
+async fn exec_up(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr>
+where
+ M: MigratorTrait + ?Sized,
+{
+ let db = manager.get_connection();
+
+ M::install(db).await?;
+ /*
+ if let Some(steps) = steps {
+ info!("Applying {} pending migrations", steps);
+ } else {
+ info!("Applying all pending migrations");
+ }
+ */
+ let migrations = M::get_pending_migrations(db).await?.into_iter();
+ /*
+ if migrations.len() == 0 {
+ info!("No pending migrations");
+ }
+ */
+ for Migration { migration, .. } in migrations {
+ if let Some(steps) = steps.as_mut() {
+ if steps == &0 {
+ break;
+ }
+ *steps -= 1;
+ }
+ info!("Applying migration '{}'", migration.name());
+ migration.up(manager).await?;
+ info!("Migration '{}' has been applied", migration.name());
+ let now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .expect("SystemTime before UNIX EPOCH!");
+ seaql_migrations::Entity::insert(seaql_migrations::ActiveModel {
+ version: ActiveValue::Set(migration.name().to_owned()),
+ applied_at: ActiveValue::Set(now.as_secs() as i64),
+ })
+ .table_name(M::migration_table_name())
+ .exec(db)
+ .await?;
+ }
+
+ Ok(())
+}
+
+async fn exec_down(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr>
+where
+ M: MigratorTrait + ?Sized,
+{
+ let db = manager.get_connection();
+
+ M::install(db).await?;
+
+ if let Some(steps) = steps {
+ info!("Rolling back {} applied migrations", steps);
+ } else {
+ info!("Rolling back all applied migrations");
+ }
+
+ let migrations = M::get_applied_migrations(db).await?.into_iter().rev();
+ if migrations.len() == 0 {
+ info!("No applied migrations");
+ }
+ for Migration { migration, .. } in migrations {
+ if let Some(steps) = steps.as_mut() {
+ if steps == &0 {
+ break;
+ }
+ *steps -= 1;
+ }
+ info!("Rolling back migration '{}'", migration.name());
+ migration.down(manager).await?;
+ info!("Migration '{}' has been rollbacked", migration.name());
+ seaql_migrations::Entity::delete_many()
+ .filter(Expr::col(seaql_migrations::Column::Version).eq(migration.name()))
+ .table_name(M::migration_table_name())
+ .exec(db)
+ .await?;
+ }
+
+ Ok(())
+}
+
+async fn query_tables(db: &C) -> SelectStatement
+where
+ C: ConnectionTrait,
+{
+ match db.get_database_backend() {
+ DbBackend::MySql => MySql.query_tables(),
+ DbBackend::Postgres => Postgres.query_tables(),
+ DbBackend::Sqlite => Sqlite.query_tables(),
+ }
+}
+
+fn get_current_schema(db: &C) -> SimpleExpr
+where
+ C: ConnectionTrait,
+{
+ match db.get_database_backend() {
+ DbBackend::MySql => MySql::get_current_schema(),
+ DbBackend::Postgres => Postgres::get_current_schema(),
+ DbBackend::Sqlite => unimplemented!(),
+ }
+}
+
+#[derive(DeriveIden)]
+enum InformationSchema {
+ #[sea_orm(iden = "information_schema")]
+ Schema,
+ #[sea_orm(iden = "TABLE_NAME")]
+ TableName,
+ #[sea_orm(iden = "CONSTRAINT_NAME")]
+ ConstraintName,
+ TableConstraints,
+ TableSchema,
+ ConstraintType,
+}
+
+fn query_mysql_foreign_keys(db: &C) -> SelectStatement
+where
+ C: ConnectionTrait,
+{
+ let mut stmt = Query::select();
+ stmt.columns([
+ InformationSchema::TableName,
+ InformationSchema::ConstraintName,
+ ])
+ .from((
+ InformationSchema::Schema,
+ InformationSchema::TableConstraints,
+ ))
+ .cond_where(
+ Condition::all()
+ .add(Expr::expr(get_current_schema(db)).equals((
+ InformationSchema::TableConstraints,
+ InformationSchema::TableSchema,
+ )))
+ .add(
+ Expr::col((
+ InformationSchema::TableConstraints,
+ InformationSchema::ConstraintType,
+ ))
+ .eq("FOREIGN KEY"),
+ ),
+ );
+ stmt
+}
+
+#[derive(DeriveIden)]
+enum PgType {
+ Table,
+ Typname,
+ Typnamespace,
+ Typelem,
+}
+
+#[derive(DeriveIden)]
+enum PgNamespace {
+ Table,
+ Oid,
+ Nspname,
+}
+
+fn query_pg_types(db: &C) -> SelectStatement
+where
+ C: ConnectionTrait,
+{
+ let mut stmt = Query::select();
+ stmt.column(PgType::Typname)
+ .from(PgType::Table)
+ .join(
+ JoinType::LeftJoin,
+ PgNamespace::Table,
+ Expr::col((PgNamespace::Table, PgNamespace::Oid))
+ .equals((PgType::Table, PgType::Typnamespace)),
+ )
+ .cond_where(
+ Condition::all()
+ .add(
+ Expr::expr(get_current_schema(db))
+ .equals((PgNamespace::Table, PgNamespace::Nspname)),
+ )
+ .add(Expr::col((PgType::Table, PgType::Typelem)).eq(0)),
+ );
+ stmt
+}
+
+trait QueryTable {
+ type Statement;
+
+ fn table_name(self, table_name: DynIden) -> Self::Statement;
+}
+
+impl QueryTable for SelectStatement {
+ type Statement = SelectStatement;
+
+ fn table_name(mut self, table_name: DynIden) -> SelectStatement {
+ self.from(table_name);
+ self
+ }
+}
+
+impl QueryTable for sea_query::TableCreateStatement {
+ type Statement = sea_query::TableCreateStatement;
+
+ fn table_name(mut self, table_name: DynIden) -> sea_query::TableCreateStatement {
+ self.table(table_name);
+ self
+ }
+}
+
+impl QueryTable for sea_orm::Insert
+where
+ A: ActiveModelTrait,
+{
+ type Statement = sea_orm::Insert ;
+
+ fn table_name(mut self, table_name: DynIden) -> sea_orm::Insert {
+ sea_orm::QueryTrait::query(&mut self).into_table(table_name);
+ self
+ }
+}
+
+impl QueryTable for sea_orm::DeleteMany
+where
+ E: EntityTrait,
+{
+ type Statement = sea_orm::DeleteMany;
+
+ fn table_name(mut self, table_name: DynIden) -> sea_orm::DeleteMany {
+ sea_orm::QueryTrait::query(&mut self).from_table(table_name);
+ self
+ }
+}
diff --git a/packages/pagetop-seaorm/src/db/migration/prelude.rs b/packages/pagetop-seaorm/src/db/migration/prelude.rs
new file mode 100644
index 00000000..5556a094
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/prelude.rs
@@ -0,0 +1,13 @@
+//pub use super::cli;
+
+pub use super::connection::IntoSchemaManagerConnection;
+pub use super::connection::SchemaManagerConnection;
+pub use super::manager::SchemaManager;
+pub use super::migrator::MigratorTrait;
+pub use super::{MigrationName, MigrationTrait};
+pub use async_trait;
+pub use sea_orm;
+pub use sea_orm::sea_query;
+pub use sea_orm::sea_query::*;
+pub use sea_orm::DeriveIden;
+pub use sea_orm::DeriveMigrationName;
diff --git a/packages/pagetop-seaorm/src/db/migration/schema.rs b/packages/pagetop-seaorm/src/db/migration/schema.rs
new file mode 100644
index 00000000..340b7482
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/schema.rs
@@ -0,0 +1,608 @@
+//! > Adapted from https://github.com/loco-rs/loco/blob/master/src/schema.rs
+//!
+//! # Database Table Schema Helpers
+//!
+//! This module defines functions and helpers for creating database table
+//! schemas using the `sea-orm` and `sea-query` libraries.
+//!
+//! # Example
+//!
+//! The following example shows how the user migration file should be and using
+//! the schema helpers to create the Db fields.
+//!
+//! ```rust
+//! use sea_orm_migration::{prelude::*, schema::*};
+//!
+//! #[derive(DeriveMigrationName)]
+//! pub struct Migration;
+//!
+//! #[async_trait::async_trait]
+//! impl MigrationTrait for Migration {
+//! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+//! let table = table_auto(Users::Table)
+//! .col(pk_auto(Users::Id))
+//! .col(uuid(Users::Pid))
+//! .col(string_uniq(Users::Email))
+//! .col(string(Users::Password))
+//! .col(string(Users::Name))
+//! .col(string_null(Users::ResetToken))
+//! .col(timestamp_null(Users::ResetSentAt))
+//! .to_owned();
+//! manager.create_table(table).await?;
+//! Ok(())
+//! }
+//!
+//! async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+//! manager
+//! .drop_table(Table::drop().table(Users::Table).to_owned())
+//! .await
+//! }
+//! }
+//!
+//! #[derive(Iden)]
+//! pub enum Users {
+//! Table,
+//! Id,
+//! Pid,
+//! Email,
+//! Name,
+//! Password,
+//! ResetToken,
+//! ResetSentAt,
+//! }
+//! ```
+
+use crate::prelude::Iden;
+use sea_orm::sea_query::{
+ self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement,
+};
+
+#[derive(Iden)]
+enum GeneralIds {
+ CreatedAt,
+ UpdatedAt,
+}
+
+/// Wrapping table schema creation.
+pub fn table_auto(name: T) -> TableCreateStatement {
+ timestamps(Table::create().table(name).if_not_exists().take())
+}
+
+/// Create a primary key column with auto-increment feature.
+pub fn pk_auto(name: T) -> ColumnDef {
+ integer(name).auto_increment().primary_key().take()
+}
+
+pub fn char_len(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).char_len(length).not_null().take()
+}
+
+pub fn char_len_null(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).char_len(length).null().take()
+}
+
+pub fn char_len_uniq(col: T, length: u32) -> ColumnDef {
+ char_len(col, length).unique_key().take()
+}
+
+pub fn char(col: T) -> ColumnDef {
+ ColumnDef::new(col).char().not_null().take()
+}
+
+pub fn char_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).char().null().take()
+}
+
+pub fn char_uniq(col: T) -> ColumnDef {
+ char(col).unique_key().take()
+}
+
+pub fn string_len(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).string_len(length).not_null().take()
+}
+
+pub fn string_len_null(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).string_len(length).null().take()
+}
+
+pub fn string_len_uniq(col: T, length: u32) -> ColumnDef {
+ string_len(col, length).unique_key().take()
+}
+
+pub fn string(col: T) -> ColumnDef {
+ ColumnDef::new(col).string().not_null().take()
+}
+
+pub fn string_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).string().null().take()
+}
+
+pub fn string_uniq(col: T) -> ColumnDef {
+ string(col).unique_key().take()
+}
+
+pub fn text(col: T) -> ColumnDef {
+ ColumnDef::new(col).text().not_null().take()
+}
+
+pub fn text_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).text().null().take()
+}
+
+pub fn text_uniq(col: T) -> ColumnDef {
+ text(col).unique_key().take()
+}
+
+pub fn tiny_integer(col: T) -> ColumnDef {
+ ColumnDef::new(col).tiny_integer().not_null().take()
+}
+
+pub fn tiny_integer_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).tiny_integer().null().take()
+}
+
+pub fn tiny_integer_uniq(col: T) -> ColumnDef {
+ tiny_integer(col).unique_key().take()
+}
+
+pub fn small_integer(col: T) -> ColumnDef {
+ ColumnDef::new(col).small_integer().not_null().take()
+}
+
+pub fn small_integer_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).small_integer().null().take()
+}
+
+pub fn small_integer_uniq(col: T) -> ColumnDef {
+ small_integer(col).unique_key().take()
+}
+
+pub fn integer(col: T) -> ColumnDef {
+ ColumnDef::new(col).integer().not_null().take()
+}
+
+pub fn integer_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).integer().null().take()
+}
+
+pub fn integer_uniq(col: T) -> ColumnDef {
+ integer(col).unique_key().take()
+}
+
+pub fn big_integer(col: T) -> ColumnDef {
+ ColumnDef::new(col).big_integer().not_null().take()
+}
+
+pub fn big_integer_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).big_integer().null().take()
+}
+
+pub fn big_integer_uniq(col: T) -> ColumnDef {
+ big_integer(col).unique_key().take()
+}
+
+pub fn tiny_unsigned(col: T) -> ColumnDef {
+ ColumnDef::new(col).tiny_unsigned().not_null().take()
+}
+
+pub fn tiny_unsigned_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).tiny_unsigned().null().take()
+}
+
+pub fn tiny_unsigned_uniq(col: T) -> ColumnDef {
+ tiny_unsigned(col).unique_key().take()
+}
+
+pub fn small_unsigned(col: T) -> ColumnDef {
+ ColumnDef::new(col).small_unsigned().not_null().take()
+}
+
+pub fn small_unsigned_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).small_unsigned().null().take()
+}
+
+pub fn small_unsigned_uniq(col: T) -> ColumnDef {
+ small_unsigned(col).unique_key().take()
+}
+
+pub fn unsigned(col: T) -> ColumnDef {
+ ColumnDef::new(col).unsigned().not_null().take()
+}
+
+pub fn unsigned_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).unsigned().null().take()
+}
+
+pub fn unsigned_uniq(col: T) -> ColumnDef {
+ unsigned(col).unique_key().take()
+}
+
+pub fn big_unsigned(col: T) -> ColumnDef {
+ ColumnDef::new(col).big_unsigned().not_null().take()
+}
+
+pub fn big_unsigned_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).big_unsigned().null().take()
+}
+
+pub fn big_unsigned_uniq(col: T) -> ColumnDef {
+ big_unsigned(col).unique_key().take()
+}
+
+pub fn float(col: T) -> ColumnDef {
+ ColumnDef::new(col).float().not_null().take()
+}
+
+pub fn float_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).float().null().take()
+}
+
+pub fn float_uniq(col: T) -> ColumnDef {
+ float(col).unique_key().take()
+}
+
+pub fn double(col: T) -> ColumnDef {
+ ColumnDef::new(col).double().not_null().take()
+}
+
+pub fn double_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).double().null().take()
+}
+
+pub fn double_uniq(col: T) -> ColumnDef {
+ double(col).unique_key().take()
+}
+
+pub fn decimal_len(col: T, precision: u32, scale: u32) -> ColumnDef {
+ ColumnDef::new(col)
+ .decimal_len(precision, scale)
+ .not_null()
+ .take()
+}
+
+pub fn decimal_len_null(col: T, precision: u32, scale: u32) -> ColumnDef {
+ ColumnDef::new(col)
+ .decimal_len(precision, scale)
+ .null()
+ .take()
+}
+
+pub fn decimal_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef {
+ decimal_len(col, precision, scale).unique_key().take()
+}
+
+pub fn decimal(col: T) -> ColumnDef {
+ ColumnDef::new(col).decimal().not_null().take()
+}
+
+pub fn decimal_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).decimal().null().take()
+}
+
+pub fn decimal_uniq(col: T) -> ColumnDef {
+ decimal(col).unique_key().take()
+}
+
+pub fn date_time(col: T) -> ColumnDef {
+ ColumnDef::new(col).date_time().not_null().take()
+}
+
+pub fn date_time_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).date_time().null().take()
+}
+
+pub fn date_time_uniq(col: T) -> ColumnDef {
+ date_time(col).unique_key().take()
+}
+
+pub fn interval(
+ col: T,
+ fields: Option,
+ precision: Option,
+) -> ColumnDef {
+ ColumnDef::new(col)
+ .interval(fields, precision)
+ .not_null()
+ .take()
+}
+
+pub fn interval_null(
+ col: T,
+ fields: Option,
+ precision: Option,
+) -> ColumnDef {
+ ColumnDef::new(col)
+ .interval(fields, precision)
+ .null()
+ .take()
+}
+
+pub fn interval_uniq(
+ col: T,
+ fields: Option,
+ precision: Option,
+) -> ColumnDef {
+ interval(col, fields, precision).unique_key().take()
+}
+
+pub fn timestamp(col: T) -> ColumnDef {
+ ColumnDef::new(col).timestamp().not_null().take()
+}
+
+pub fn timestamp_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).timestamp().null().take()
+}
+
+pub fn timestamp_uniq(col: T) -> ColumnDef {
+ timestamp(col).unique_key().take()
+}
+
+pub fn timestamp_with_time_zone(col: T) -> ColumnDef {
+ ColumnDef::new(col)
+ .timestamp_with_time_zone()
+ .not_null()
+ .take()
+}
+
+pub fn timestamp_with_time_zone_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).timestamp_with_time_zone().null().take()
+}
+
+pub fn timestamp_with_time_zone_uniq(col: T) -> ColumnDef {
+ timestamp_with_time_zone(col).unique_key().take()
+}
+
+pub fn time(col: T) -> ColumnDef {
+ ColumnDef::new(col).time().not_null().take()
+}
+
+pub fn time_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).time().null().take()
+}
+
+pub fn time_uniq(col: T) -> ColumnDef {
+ time(col).unique_key().take()
+}
+
+pub fn date(col: T) -> ColumnDef {
+ ColumnDef::new(col).date().not_null().take()
+}
+
+pub fn date_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).date().null().take()
+}
+
+pub fn date_uniq(col: T) -> ColumnDef {
+ date(col).unique_key().take()
+}
+
+pub fn year(col: T) -> ColumnDef {
+ ColumnDef::new(col).year().not_null().take()
+}
+
+pub fn year_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).year().null().take()
+}
+
+pub fn year_uniq(col: T) -> ColumnDef {
+ year(col).unique_key().take()
+}
+
+pub fn binary_len(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).binary_len(length).not_null().take()
+}
+
+pub fn binary_len_null(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).binary_len(length).null().take()
+}
+
+pub fn binary_len_uniq(col: T, length: u32) -> ColumnDef {
+ binary_len(col, length).unique_key().take()
+}
+
+pub fn binary(col: T) -> ColumnDef {
+ ColumnDef::new(col).binary().not_null().take()
+}
+
+pub fn binary_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).binary().null().take()
+}
+
+pub fn binary_uniq(col: T) -> ColumnDef {
+ binary(col).unique_key().take()
+}
+
+pub fn var_binary(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).var_binary(length).not_null().take()
+}
+
+pub fn var_binary_null(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).var_binary(length).null().take()
+}
+
+pub fn var_binary_uniq(col: T, length: u32) -> ColumnDef {
+ var_binary(col, length).unique_key().take()
+}
+
+pub fn bit(col: T, length: Option) -> ColumnDef {
+ ColumnDef::new(col).bit(length).not_null().take()
+}
+
+pub fn bit_null(col: T, length: Option) -> ColumnDef {
+ ColumnDef::new(col).bit(length).null().take()
+}
+
+pub fn bit_uniq(col: T, length: Option) -> ColumnDef {
+ bit(col, length).unique_key().take()
+}
+
+pub fn varbit(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).varbit(length).not_null().take()
+}
+
+pub fn varbit_null(col: T, length: u32) -> ColumnDef {
+ ColumnDef::new(col).varbit(length).null().take()
+}
+
+pub fn varbit_uniq(col: T, length: u32) -> ColumnDef {
+ varbit(col, length).unique_key().take()
+}
+
+pub fn blob(col: T) -> ColumnDef {
+ ColumnDef::new(col).blob().not_null().take()
+}
+
+pub fn blob_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).blob().null().take()
+}
+
+pub fn blob_uniq(col: T) -> ColumnDef {
+ blob(col).unique_key().take()
+}
+
+pub fn boolean(col: T) -> ColumnDef {
+ ColumnDef::new(col).boolean().not_null().take()
+}
+
+pub fn boolean_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).boolean().null().take()
+}
+
+pub fn boolean_uniq(col: T) -> ColumnDef {
+ boolean(col).unique_key().take()
+}
+
+pub fn money_len(col: T, precision: u32, scale: u32) -> ColumnDef {
+ ColumnDef::new(col)
+ .money_len(precision, scale)
+ .not_null()
+ .take()
+}
+
+pub fn money_len_null(col: T, precision: u32, scale: u32) -> ColumnDef {
+ ColumnDef::new(col)
+ .money_len(precision, scale)
+ .null()
+ .take()
+}
+
+pub fn money_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef {
+ money_len(col, precision, scale).unique_key().take()
+}
+
+pub fn money(col: T) -> ColumnDef {
+ ColumnDef::new(col).money().not_null().take()
+}
+
+pub fn money_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).money().null().take()
+}
+
+pub fn money_uniq(col: T) -> ColumnDef {
+ money(col).unique_key().take()
+}
+
+pub fn json(col: T) -> ColumnDef {
+ ColumnDef::new(col).json().not_null().take()
+}
+
+pub fn json_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).json().null().take()
+}
+
+pub fn json_uniq(col: T) -> ColumnDef {
+ json(col).unique_key().take()
+}
+
+pub fn json_binary(col: T) -> ColumnDef {
+ ColumnDef::new(col).json_binary().not_null().take()
+}
+
+pub fn json_binary_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).json_binary().null().take()
+}
+
+pub fn json_binary_uniq(col: T) -> ColumnDef {
+ json_binary(col).unique_key().take()
+}
+
+pub fn uuid(col: T) -> ColumnDef {
+ ColumnDef::new(col).uuid().not_null().take()
+}
+
+pub fn uuid_null(col: T) -> ColumnDef {
+ ColumnDef::new(col).uuid().null().take()
+}
+
+pub fn uuid_uniq(col: T) -> ColumnDef {
+ uuid(col).unique_key().take()
+}
+
+pub fn custom(col: T, name: T) -> ColumnDef {
+ ColumnDef::new(col).custom(name).not_null().take()
+}
+
+pub fn custom_null(col: T, name: T) -> ColumnDef {
+ ColumnDef::new(col).custom(name).null().take()
+}
+
+pub fn enumeration(col: T, name: N, variants: V) -> ColumnDef
+where
+ T: IntoIden,
+ N: IntoIden,
+ S: IntoIden,
+ V: IntoIterator- ,
+{
+ ColumnDef::new(col)
+ .enumeration(name, variants)
+ .not_null()
+ .take()
+}
+
+pub fn enumeration_null
(col: T, name: N, variants: V) -> ColumnDef
+where
+ T: IntoIden,
+ N: IntoIden,
+ S: IntoIden,
+ V: IntoIterator- ,
+{
+ ColumnDef::new(col)
+ .enumeration(name, variants)
+ .null()
+ .take()
+}
+
+pub fn enumeration_uniq
(col: T, name: N, variants: V) -> ColumnDef
+where
+ T: IntoIden,
+ N: IntoIden,
+ S: IntoIden,
+ V: IntoIterator- ,
+{
+ enumeration(col, name, variants).unique_key().take()
+}
+
+pub fn array
(col: T, elem_type: ColumnType) -> ColumnDef {
+ ColumnDef::new(col).array(elem_type).not_null().take()
+}
+
+pub fn array_null(col: T, elem_type: ColumnType) -> ColumnDef {
+ ColumnDef::new(col).array(elem_type).null().take()
+}
+
+pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef {
+ array(col, elem_type).unique_key().take()
+}
+
+/// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table.
+pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement {
+ let mut t = t;
+ t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp()))
+ .col(timestamp(GeneralIds::UpdatedAt).default(Expr::current_timestamp()))
+ .take()
+}
+
+/// Create an Alias.
+pub fn name>(name: T) -> Alias {
+ Alias::new(name)
+}
diff --git a/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs b/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs
new file mode 100644
index 00000000..51da9300
--- /dev/null
+++ b/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs
@@ -0,0 +1,15 @@
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+// One should override the name of migration table via `MigratorTrait::migration_table_name` method
+#[sea_orm(table_name = "seaql_migrations")]
+pub struct Model {
+ #[sea_orm(primary_key, auto_increment = false)]
+ pub version: String,
+ pub applied_at: i64,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/pagetop-seaorm/src/lib.rs b/packages/pagetop-seaorm/src/lib.rs
new file mode 100644
index 00000000..9bd396dd
--- /dev/null
+++ b/packages/pagetop-seaorm/src/lib.rs
@@ -0,0 +1,31 @@
+use pagetop::prelude::*;
+
+use std::sync::LazyLock;
+
+pub mod config;
+pub mod db;
+
+/// The package Prelude.
+pub mod prelude {
+ pub use crate::db::*;
+ pub use crate::install_migrations;
+}
+
+include_locales!(LOCALES_SEAORM);
+
+/// Implements [`PackageTrait`](pagetop::core::package::PackageTrait) and specific package API.
+pub struct SeaORM;
+
+impl PackageTrait for SeaORM {
+ fn name(&self) -> L10n {
+ L10n::t("package_name", &LOCALES_SEAORM)
+ }
+
+ fn description(&self) -> L10n {
+ L10n::t("package_description", &LOCALES_SEAORM)
+ }
+
+ fn init(&self) {
+ LazyLock::force(&db::DBCONN);
+ }
+}
diff --git a/packages/pagetop-seaorm/src/locale/en-US/package.ftl b/packages/pagetop-seaorm/src/locale/en-US/package.ftl
new file mode 100644
index 00000000..d1c53cc1
--- /dev/null
+++ b/packages/pagetop-seaorm/src/locale/en-US/package.ftl
@@ -0,0 +1,2 @@
+package_name = SeaORM support
+package_description = Integrate SeaORM as the database framework for PageTop applications.
diff --git a/packages/pagetop-seaorm/src/locale/es-ES/package.ftl b/packages/pagetop-seaorm/src/locale/es-ES/package.ftl
new file mode 100644
index 00000000..bd7c004b
--- /dev/null
+++ b/packages/pagetop-seaorm/src/locale/es-ES/package.ftl
@@ -0,0 +1,2 @@
+package_name = Soporte a SeaORM
+package_description = Integra SeaORM como framework de base de datos para aplicaciones PageTop.