generated from t/malachite
add: full initial
This commit is contained in:
parent
f5c663495d
commit
d06bc5e726
29 changed files with 592 additions and 1928 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
target/
|
target/
|
||||||
|
app.toml
|
||||||
|
/buckets
|
||||||
|
|
647
Cargo.lock
generated
647
Cargo.lock
generated
|
@ -146,29 +146,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-extra"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"axum-core",
|
|
||||||
"bytes",
|
|
||||||
"cookie",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"mime",
|
|
||||||
"pin-project-lite",
|
|
||||||
"rustversion",
|
|
||||||
"serde",
|
|
||||||
"tower",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -257,13 +234,31 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "buckets"
|
||||||
version = "1.12.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"axum",
|
||||||
|
"buckets-core",
|
||||||
|
"dotenv",
|
||||||
|
"pathbufd",
|
||||||
|
"tetratto-core",
|
||||||
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "buckets-core"
|
||||||
|
version = "1.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"oiseau",
|
||||||
|
"pathbufd",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tetratto-core",
|
||||||
|
"tetratto-shared",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -325,28 +320,6 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono-tz"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"chrono-tz-build",
|
|
||||||
"phf 0.11.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono-tz-build"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
|
||||||
dependencies = [
|
|
||||||
"parse-zoneinfo",
|
|
||||||
"phf 0.11.3",
|
|
||||||
"phf_codegen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
|
@ -367,17 +340,6 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cookie"
|
|
||||||
version = "0.18.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
|
||||||
dependencies = [
|
|
||||||
"percent-encoding",
|
|
||||||
"time",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -412,31 +374,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-deque"
|
|
||||||
version = "0.8.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-epoch"
|
|
||||||
version = "0.9.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.8.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -476,21 +413,6 @@ version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deranged"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
|
||||||
dependencies = [
|
|
||||||
"powerfmt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deunicode"
|
|
||||||
version = "1.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -534,12 +456,6 @@ dependencies = [
|
||||||
"dtoa",
|
"dtoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emojis"
|
name = "emojis"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -571,7 +487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -760,41 +676,11 @@ version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "globset"
|
|
||||||
version = "0.4.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"bstr",
|
|
||||||
"log",
|
|
||||||
"regex-automata 0.4.9",
|
|
||||||
"regex-syntax 0.8.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "globwalk"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.2",
|
|
||||||
"ignore",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.11"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
|
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -893,15 +779,6 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humansize"
|
|
||||||
version = "2.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
|
||||||
dependencies = [
|
|
||||||
"libm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1112,22 +989,6 @@ dependencies = [
|
||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ignore"
|
|
||||||
version = "0.4.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-deque",
|
|
||||||
"globset",
|
|
||||||
"log",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata 0.4.9",
|
|
||||||
"same-file",
|
|
||||||
"walkdir",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.6"
|
version = "0.25.6"
|
||||||
|
@ -1148,7 +1009,6 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1178,15 +1038,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
@ -1215,12 +1066,6 @@ version = "0.2.174"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1268,31 +1113,6 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "malachite"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"axum-extra",
|
|
||||||
"dotenv",
|
|
||||||
"glob",
|
|
||||||
"nanoneo",
|
|
||||||
"oiseau",
|
|
||||||
"pathbufd",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_valid",
|
|
||||||
"tera",
|
|
||||||
"tetratto-core",
|
|
||||||
"tetratto-shared",
|
|
||||||
"tokio",
|
|
||||||
"toml 0.9.5",
|
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maplit"
|
name = "maplit"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -1389,12 +1209,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nanoneo"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1495d19c5bed5372c613d7b4a38e8093b357f4405ce38ba1de2d6586e5c892"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -1438,12 +1252,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-conv"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
|
@ -1562,15 +1370,6 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse-zoneinfo"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
|
||||||
dependencies = [
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
@ -1592,50 +1391,6 @@ version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"thiserror 2.0.12",
|
|
||||||
"ucd-trie",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_derive"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"pest_generator",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_generator"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"pest_meta",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pest_meta"
|
|
||||||
version = "2.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
"sha2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
@ -1775,12 +1530,6 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "powerfmt"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
@ -1796,27 +1545,6 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[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",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
|
@ -1883,8 +1611,6 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
|
||||||
"rand_chacha 0.3.1",
|
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1894,20 +1620,10 @@ version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha",
|
||||||
"rand_core 0.9.3",
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1923,9 +1639,6 @@ name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.16",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
|
@ -2083,14 +1796,14 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.29"
|
version = "0.23.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
|
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
@ -2131,15 +1844,6 @@ version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.27"
|
version = "0.1.27"
|
||||||
|
@ -2226,15 +1930,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -2256,52 +1951,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_valid"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b615bed66931a7a9809b273937adc8a402d038b1e509d027fcaf62f084d33d1"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"itertools",
|
|
||||||
"num-traits",
|
|
||||||
"once_cell",
|
|
||||||
"paste",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_valid_derive",
|
|
||||||
"serde_valid_literal",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"toml 0.8.23",
|
|
||||||
"unicode-segmentation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_valid_derive"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5fa1a5a21ea5aab06d2e6a6b59837d450fb2be9695be97735a711edfbe79ea07"
|
|
||||||
dependencies = [
|
|
||||||
"itertools",
|
|
||||||
"paste",
|
|
||||||
"proc-macro-error2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"strsim",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_valid_literal"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dd07331596ea967dccf9a35bde71ecd757490e09827b938a5c6226c648e3a25e"
|
|
||||||
dependencies = [
|
|
||||||
"paste",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -2363,16 +2012,6 @@ version = "0.4.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slug"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
|
|
||||||
dependencies = [
|
|
||||||
"deunicode",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
|
@ -2450,12 +2089,6 @@ dependencies = [
|
||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
@ -2516,15 +2149,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.20.0"
|
version = "3.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2538,33 +2171,11 @@ dependencies = [
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tera"
|
|
||||||
version = "1.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"chrono-tz",
|
|
||||||
"globwalk",
|
|
||||||
"humansize",
|
|
||||||
"lazy_static",
|
|
||||||
"percent-encoding",
|
|
||||||
"pest",
|
|
||||||
"pest_derive",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"slug",
|
|
||||||
"unic-segment",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tetratto-core"
|
name = "tetratto-core"
|
||||||
version = "15.0.1"
|
version = "15.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7aeb9dcc5631ec6188bb9438dc97015c6662b6f59e650e5afa865775f170c9c"
|
checksum = "605c03fac71468f57f9c47d9246300640f3f65ec9f19fb86799e10f632d3ea68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"base16ct",
|
"base16ct",
|
||||||
|
@ -2582,7 +2193,7 @@ dependencies = [
|
||||||
"tetratto-l10n",
|
"tetratto-l10n",
|
||||||
"tetratto-shared",
|
"tetratto-shared",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.9.5",
|
"toml",
|
||||||
"totp-rs",
|
"totp-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2594,7 +2205,7 @@ checksum = "d96f5e41633c757e3519efb47c9b85d00d14322c1961360e126d0ecc0ea79b86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pathbufd",
|
"pathbufd",
|
||||||
"serde",
|
"serde",
|
||||||
"toml 0.9.5",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2615,33 +2226,13 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2664,37 +2255,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.3.41"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
|
||||||
dependencies = [
|
|
||||||
"deranged",
|
|
||||||
"itoa",
|
|
||||||
"num-conv",
|
|
||||||
"powerfmt",
|
|
||||||
"serde",
|
|
||||||
"time-core",
|
|
||||||
"time-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time-core"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time-macros"
|
|
||||||
version = "0.2.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
|
||||||
dependencies = [
|
|
||||||
"num-conv",
|
|
||||||
"time-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -2821,18 +2381,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.8.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned 0.6.9",
|
|
||||||
"toml_datetime 0.6.11",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
@ -2841,22 +2389,13 @@ checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned 1.0.0",
|
"serde_spanned",
|
||||||
"toml_datetime 0.7.0",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"toml_writer",
|
"toml_writer",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -2866,20 +2405,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned 0.6.9",
|
|
||||||
"toml_datetime 0.6.11",
|
|
||||||
"toml_write",
|
|
||||||
"winnow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -2889,12 +2414,6 @@ dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_write"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_writer"
|
name = "toml_writer"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -3055,7 +2574,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3065,62 +2584,6 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ucd-trie"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-char-property"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
|
||||||
dependencies = [
|
|
||||||
"unic-char-range",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-char-range"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-common"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-segment"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
|
|
||||||
dependencies = [
|
|
||||||
"unic-ucd-segment",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-ucd-segment"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
|
|
||||||
dependencies = [
|
|
||||||
"unic-char-property",
|
|
||||||
"unic-char-range",
|
|
||||||
"unic-ucd-version",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-ucd-version"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
|
||||||
dependencies = [
|
|
||||||
"unic-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
|
@ -3154,12 +2617,6 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-segmentation"
|
|
||||||
version = "1.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -3230,16 +2687,6 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -3390,15 +2837,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -3628,9 +3066,6 @@ name = "winnow"
|
||||||
version = "0.7.12"
|
version = "0.7.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
|
|
51
Cargo.toml
51
Cargo.toml
|
@ -1,34 +1,19 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "malachite"
|
resolver = "2"
|
||||||
version = "1.0.0"
|
members = ["crates/buckets", "crates/buckets-core"]
|
||||||
edition = "2024"
|
package.authors = ["trisuaso"]
|
||||||
authors = ["trisuaso"]
|
package.repository = "https://trisua.com/t/buckets"
|
||||||
repository = "https://trisua.com/t/malachite"
|
package.license = "AGPL-3.0-or-later"
|
||||||
license = "AGPL-3.0-or-later"
|
package.homepage = "https://tetratto.com"
|
||||||
homepage = "https://trisua.com"
|
|
||||||
|
|
||||||
[dependencies]
|
[profile.dev]
|
||||||
tetratto-core = "15.0.1"
|
incremental = true
|
||||||
tetratto-shared = "12.0.6"
|
|
||||||
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
[profile.release]
|
||||||
pathbufd = "0.1.4"
|
opt-level = 3
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
lto = true
|
||||||
tera = "1.20.0"
|
codegen-units = 2
|
||||||
tracing = "0.1.41"
|
# panic = "abort"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
panic = "unwind"
|
||||||
tower-http = { version = "0.6.6", features = [
|
strip = true
|
||||||
"trace",
|
incremental = true
|
||||||
"fs",
|
|
||||||
"catch-panic",
|
|
||||||
"set-header",
|
|
||||||
] }
|
|
||||||
axum = { version = "0.8.4", features = ["macros", "ws"] }
|
|
||||||
axum-extra = { version = "0.10.1", features = ["cookie"] }
|
|
||||||
nanoneo = "0.2.0"
|
|
||||||
dotenv = "0.15.0"
|
|
||||||
glob = "0.3.2"
|
|
||||||
serde_json = "1.0.142"
|
|
||||||
toml = "0.9.4"
|
|
||||||
serde_valid = { version = "1.0.5", features = ["toml"] }
|
|
||||||
regex = "1.11.1"
|
|
||||||
oiseau = { version = "0.1.2", default-features = false, features = ["postgres", "redis",] }
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
# 🪨 malachite
|
# 🪣 buckets
|
||||||
|
|
||||||
simple template for building backends with a structure similar to how the [tetratto](https://trisua.com/t/tetratto) repository is organized
|
object-like storage http server
|
||||||
|
|
||||||
|
it's basically like regular file storage, but it connects to your database to store file metadata, buckets, ids, etc. (meaning files are stored in a jumbled mess on the file system, but are controlled by entries in the database!)
|
||||||
|
|
7
app/.gitignore
vendored
7
app/.gitignore
vendored
|
@ -1,7 +0,0 @@
|
||||||
docs/**/*
|
|
||||||
!docs/example.md
|
|
||||||
icons/
|
|
||||||
templates_build/
|
|
||||||
public/favicon.svg
|
|
||||||
.env
|
|
||||||
app.toml
|
|
|
@ -1 +0,0 @@
|
||||||
# hi :)
|
|
|
@ -1,208 +0,0 @@
|
||||||
// theme preference
|
|
||||||
function media_theme_pref() {
|
|
||||||
document.documentElement.removeAttribute("class");
|
|
||||||
|
|
||||||
if (
|
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches &&
|
|
||||||
(!window.localStorage.getItem("malachite.app:theme") ||
|
|
||||||
window.localStorage.getItem("malachite.app:theme") === "Auto")
|
|
||||||
) {
|
|
||||||
document.documentElement.classList.add("dark");
|
|
||||||
|
|
||||||
document.getElementById("switch_light").classList.add("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.remove("hidden");
|
|
||||||
} else if (
|
|
||||||
window.matchMedia("(prefers-color-scheme: light)").matches &&
|
|
||||||
(!window.localStorage.getItem("malachite.app:theme") ||
|
|
||||||
window.localStorage.getItem("malachite.app:theme") === "Auto")
|
|
||||||
) {
|
|
||||||
document.documentElement.classList.remove("dark");
|
|
||||||
|
|
||||||
document.getElementById("switch_light").classList.remove("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.add("hidden");
|
|
||||||
} else if (window.localStorage.getItem("malachite.app:theme")) {
|
|
||||||
/* restore theme */
|
|
||||||
const current = window.localStorage.getItem("malachite.app:theme");
|
|
||||||
document.documentElement.className = current.toLowerCase();
|
|
||||||
|
|
||||||
if (current === "Light") {
|
|
||||||
document.getElementById("switch_light").classList.remove("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.add("hidden");
|
|
||||||
} else {
|
|
||||||
document.getElementById("switch_light").classList.add("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.remove("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.temporary_set_theme = (theme) => {
|
|
||||||
document.documentElement.className = theme.toLowerCase();
|
|
||||||
|
|
||||||
if (theme === "Light") {
|
|
||||||
document.getElementById("switch_light").classList.remove("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.add("hidden");
|
|
||||||
} else {
|
|
||||||
document.getElementById("switch_light").classList.add("hidden");
|
|
||||||
document.getElementById("switch_dark").classList.remove("hidden");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis.set_theme = (theme) => {
|
|
||||||
window.localStorage.setItem("malachite.app:theme", theme);
|
|
||||||
document.documentElement.className = theme;
|
|
||||||
media_theme_pref();
|
|
||||||
};
|
|
||||||
|
|
||||||
media_theme_pref();
|
|
||||||
|
|
||||||
// messages
|
|
||||||
function get_cookie(key) {
|
|
||||||
return (document.cookie.split(`${key}=`)[1] || "").split(";")[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_message() {
|
|
||||||
const element = document.getElementById("messages");
|
|
||||||
|
|
||||||
const message = get_cookie("App-Message");
|
|
||||||
const message_good = get_cookie("App-Message-Good") === "true";
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
element.style.marginBottom = "1rem";
|
|
||||||
element.style.paddingLeft = "1rem";
|
|
||||||
element.innerHTML = `<li class="${message_good ? "green" : "red"}">${message.replaceAll('"', "")}</li>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear cookies
|
|
||||||
for (cookie of document.cookie.split(";")) {
|
|
||||||
// biome-ignore lint/suspicious/noDocumentCookie: cookie store is barely supported
|
|
||||||
document.cookie = `${cookie.split("=")[0]}=; expires=${new Date(0).toUTCString()}; path=/`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.show_message = (message, message_good = true) => {
|
|
||||||
const element = document.getElementById("messages");
|
|
||||||
element.style.marginBottom = "1rem";
|
|
||||||
element.style.paddingLeft = "1rem";
|
|
||||||
element.innerHTML = `<li class="${message_good ? "green" : "red"}">${message.replaceAll('"', "")}</li>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
check_message();
|
|
||||||
|
|
||||||
// components
|
|
||||||
function close_dropdowns() {
|
|
||||||
for (const dropdown of Array.from(
|
|
||||||
document.querySelectorAll(".inner.open"),
|
|
||||||
)) {
|
|
||||||
dropdown.classList.remove("open");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.open_dropdown = (event) => {
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
let target = event.target;
|
|
||||||
|
|
||||||
while (!target.matches(".dropdown")) {
|
|
||||||
target = target.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// close all others
|
|
||||||
close_dropdowns();
|
|
||||||
|
|
||||||
// open
|
|
||||||
setTimeout(() => {
|
|
||||||
for (const dropdown of Array.from(target.querySelectorAll(".inner"))) {
|
|
||||||
// check y
|
|
||||||
const box = target.getBoundingClientRect();
|
|
||||||
|
|
||||||
let parent = dropdown.parentElement;
|
|
||||||
|
|
||||||
while (!parent.matches("html, .window")) {
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent_height = parent.getBoundingClientRect().y;
|
|
||||||
|
|
||||||
if (parent.nodeName === "HTML") {
|
|
||||||
parent_height = window.screen.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scroll = window.scrollY;
|
|
||||||
const height = parent_height;
|
|
||||||
const y = box.y + scroll;
|
|
||||||
|
|
||||||
if (y > height - scroll - 375) {
|
|
||||||
dropdown.classList.add("top");
|
|
||||||
} else {
|
|
||||||
dropdown.classList.remove("top");
|
|
||||||
}
|
|
||||||
|
|
||||||
// open
|
|
||||||
dropdown.classList.add("open");
|
|
||||||
|
|
||||||
if (dropdown.classList.contains("open")) {
|
|
||||||
dropdown.removeAttribute("aria-hidden");
|
|
||||||
} else {
|
|
||||||
dropdown.setAttribute("aria-hidden", "true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 5);
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis.init_dropdowns = (bind_to) => {
|
|
||||||
for (const dropdown of Array.from(document.querySelectorAll(".inner"))) {
|
|
||||||
dropdown.setAttribute("aria-hidden", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
bind_to.addEventListener("click", (event) => {
|
|
||||||
if (
|
|
||||||
event.target.matches(".dropdown") ||
|
|
||||||
event.target.matches("[exclude=dropdown]")
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const dropdown of Array.from(
|
|
||||||
document.querySelectorAll(".inner.open"),
|
|
||||||
)) {
|
|
||||||
dropdown.classList.remove("open");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
globalThis.hash_check = (hash) => {
|
|
||||||
if (hash.startsWith("#/")) {
|
|
||||||
for (const x of Array.from(document.querySelectorAll(".subpage"))) {
|
|
||||||
x.classList.add("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById(hash).classList.remove("hidden");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("hashchange", (_) => hash_check(window.location.hash));
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// run initial hash check
|
|
||||||
hash_check(window.location.hash);
|
|
||||||
}, 150);
|
|
||||||
|
|
||||||
globalThis.submitter_load = (submitter) => {
|
|
||||||
return {
|
|
||||||
load() {
|
|
||||||
submitter.querySelector("[ui_ident=text]").classList.add("hidden");
|
|
||||||
submitter
|
|
||||||
.querySelector("[ui_ident=loader]")
|
|
||||||
.classList.remove("hidden");
|
|
||||||
submitter.setAttribute("disabled", "true");
|
|
||||||
},
|
|
||||||
failed() {
|
|
||||||
submitter
|
|
||||||
.querySelector("[ui_ident=text]")
|
|
||||||
.classList.remove("hidden");
|
|
||||||
submitter
|
|
||||||
.querySelector("[ui_ident=loader]")
|
|
||||||
.classList.add("hidden");
|
|
||||||
submitter.removeAttribute("disabled");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
../../target/doc
|
|
|
@ -1,705 +0,0 @@
|
||||||
:root {
|
|
||||||
color-scheme: light dark;
|
|
||||||
|
|
||||||
--color-super-lowered: oklch(87.1% 0.006 286.286);
|
|
||||||
--color-lowered: oklch(96.7% 0.001 286.375);
|
|
||||||
--color-surface: oklch(92.9% 0.013 255.508);
|
|
||||||
--color-raised: oklch(98.4% 0.003 247.858);
|
|
||||||
--color-super-raised: oklch(96.8% 0.007 247.896);
|
|
||||||
--color-text: hsl(0, 0%, 5%);
|
|
||||||
|
|
||||||
--color-link: #2949b2;
|
|
||||||
--color-shadow: rgba(0, 0, 0, 0.08);
|
|
||||||
--color-red: hsl(0, 84%, 40%);
|
|
||||||
--color-green: hsl(100, 84%, 20%);
|
|
||||||
--color-yellow: oklch(47% 0.157 37.304);
|
|
||||||
--color-purple: hsl(284, 84%, 20%);
|
|
||||||
--color-green-lowered: hsl(100, 84%, 15%);
|
|
||||||
--color-red-lowered: hsl(0, 84%, 35%);
|
|
||||||
|
|
||||||
--shadow-x-offset: 0;
|
|
||||||
--shadow-y-offset: 0.125rem;
|
|
||||||
--shadow-size: var(--pad-1);
|
|
||||||
|
|
||||||
--pad-1: 0.2rem;
|
|
||||||
--pad-2: 0.35rem;
|
|
||||||
--pad-3: 0.5rem;
|
|
||||||
--pad-4: 1rem;
|
|
||||||
|
|
||||||
--radius: 0.2rem;
|
|
||||||
--nav-height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark,
|
|
||||||
.dark * {
|
|
||||||
--color-super-lowered: var(--color-super-raised);
|
|
||||||
--color-lowered: var(--color-raised);
|
|
||||||
--color-surface: oklch(21% 0.006 285.885);
|
|
||||||
--color-raised: oklch(27.4% 0.006 286.033);
|
|
||||||
--color-super-raised: oklch(37% 0.013 285.805);
|
|
||||||
--color-text: hsl(0, 0%, 95%);
|
|
||||||
|
|
||||||
--color-link: #93c5fd;
|
|
||||||
--color-red: hsl(0, 94%, 82%);
|
|
||||||
--color-green: hsl(100, 94%, 82%);
|
|
||||||
--color-yellow: oklch(90.1% 0.076 70.697);
|
|
||||||
--color-purple: hsl(284, 94%, 82%);
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
line-height: 1.5;
|
|
||||||
letter-spacing: 0.15px;
|
|
||||||
font-family:
|
|
||||||
"Inter",
|
|
||||||
"Poppins",
|
|
||||||
"Roboto",
|
|
||||||
ui-sans-serif,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
system-ui,
|
|
||||||
sans-serif,
|
|
||||||
"Apple Color Emoji",
|
|
||||||
"Segoe UI Emoji",
|
|
||||||
"Segoe UI Symbol",
|
|
||||||
"Noto Color Emoji";
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-surface);
|
|
||||||
overflow: auto auto;
|
|
||||||
height: 100dvh;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
width: 80ch;
|
|
||||||
margin: var(--pad-4) auto;
|
|
||||||
padding: var(--pad-3) var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
margin: var(--pad-2) 0;
|
|
||||||
height: calc(100dvh - var(--pad-4) - var(--nav-height) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs .tab {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fadein {
|
|
||||||
animation: fadein ease-in-out 1 0.5s forwards running;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
height: var(--nav-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.sticky {
|
|
||||||
background: var(--color-raised);
|
|
||||||
position: sticky;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
|
||||||
main,
|
|
||||||
article,
|
|
||||||
nav,
|
|
||||||
header,
|
|
||||||
footer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex_collapse_rev {
|
|
||||||
flex-direction: column-reverse !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 10px auto 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content_container {
|
|
||||||
margin: 0 auto var(--pad-2);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 500px) {
|
|
||||||
.content_container {
|
|
||||||
max-width: 540px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.content_container {
|
|
||||||
max-width: 720px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
.content_container {
|
|
||||||
max-width: 960px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
article {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content_container {
|
|
||||||
max-width: 1100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* card */
|
|
||||||
.card {
|
|
||||||
padding: var(--pad-4);
|
|
||||||
background: var(--color-raised);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card_nest .card:nth-child(1) {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
padding: var(--pad-2) var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* button */
|
|
||||||
.button {
|
|
||||||
--h: 36px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--pad-2);
|
|
||||||
padding: var(--pad-2) calc(var(--pad-3) * 1.5);
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--color-raised);
|
|
||||||
color: var(--color-text);
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
width: max-content;
|
|
||||||
height: var(--h);
|
|
||||||
line-height: var(--h);
|
|
||||||
transition: background 0.15s;
|
|
||||||
text-decoration: none !important;
|
|
||||||
user-select: none;
|
|
||||||
appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:disabled {
|
|
||||||
opacity: 50%;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.small {
|
|
||||||
--h: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:not(:has(.button:hover)):not(.camo):hover {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.camo {
|
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar .button:not(.simple).camo:hover {
|
|
||||||
color: var(--color-link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.simple {
|
|
||||||
--size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: var(--pad-2) !important;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
width: var(--size);
|
|
||||||
height: var(--size);
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.surface {
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.surface.simple:is(.camo *) {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.green:not(.dark *) {
|
|
||||||
background: var(--color-green);
|
|
||||||
color: white !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--color-green-lowered) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.red:not(.dark *) {
|
|
||||||
background: var(--color-red);
|
|
||||||
color: white !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--color-red-lowered) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dropdown */
|
|
||||||
.dropdown {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner {
|
|
||||||
display: none;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
|
|
||||||
var(--color-shadow);
|
|
||||||
background: var(--color-raised);
|
|
||||||
color: inherit;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 100%;
|
|
||||||
right: 0;
|
|
||||||
width: max-content;
|
|
||||||
max-width: 15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner.left {
|
|
||||||
right: unset;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner.open {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner .button,
|
|
||||||
.dropdown .inner .title {
|
|
||||||
padding: var(--pad-3) var(--pad-4);
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner .title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown:has(.inner.open) .button:nth-child(1):not(.inner *) {
|
|
||||||
background: var(--color-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner.top {
|
|
||||||
top: unset;
|
|
||||||
bottom: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown .inner.left {
|
|
||||||
left: 0;
|
|
||||||
right: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* input */
|
|
||||||
input {
|
|
||||||
--h: 36px;
|
|
||||||
padding: var(--pad-2) calc(var(--pad-3) * 1.5);
|
|
||||||
background: var(--color-raised);
|
|
||||||
color: var(--color-text);
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
width: max-content;
|
|
||||||
transition:
|
|
||||||
background 0.15s,
|
|
||||||
border 0.15s;
|
|
||||||
height: var(--h);
|
|
||||||
line-height: var(--h);
|
|
||||||
border-left: solid 0px transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:not([type="checkbox"]):focus {
|
|
||||||
outline: solid 2px var(--color-primary);
|
|
||||||
box-shadow: 0 0 0 4px oklch(87% 0.065 274.039 / 25%);
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:user-invalid,
|
|
||||||
input[data-invalid] {
|
|
||||||
border-left: inset 5px var(--color-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.surface {
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
height: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* typo */
|
|
||||||
p,
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-bottom: var(--pad-4) !important;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_right:not(.repost) {
|
|
||||||
max-width: calc(100% - 52px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rhs {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
max-width: 250px;
|
|
||||||
overflow: hidden;
|
|
||||||
/* overflow-wrap: break-word; */
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 901px) {
|
|
||||||
.name.shorter {
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name.lg\:long {
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rhs {
|
|
||||||
width: calc(100% - 23rem) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin: var(--pad-2) 0 var(--pad-2) var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: var(--pad-2) var(--pad-4);
|
|
||||||
border-left: solid 5px var(--color-primary);
|
|
||||||
background: var(--color-surface);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
margin-bottom: var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre,
|
|
||||||
code {
|
|
||||||
font-family: "Jetbrains Mono", "Fire Code", monospace;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
font-size: 0.8rem !important;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
code * {
|
|
||||||
font-size: 0.8rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
code:not(pre *) {
|
|
||||||
padding: var(--pad-1) var(--pad-2);
|
|
||||||
background: oklch(98% 0.016 73.684 / 25%);
|
|
||||||
color: oklch(90.1% 0.076 70.697);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
white-space: break-spaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
code:not(pre *):not(.dark *) {
|
|
||||||
background: oklch(83.7% 0.128 66.29 / 25%);
|
|
||||||
color: oklch(47% 0.157 37.304);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg.icon {
|
|
||||||
stroke: currentColor;
|
|
||||||
fill: currentColor;
|
|
||||||
width: 18px;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg.icon.filled {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no_fill svg.icon {
|
|
||||||
fill: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
button svg {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border-top: solid 1px var(--color-super-lowered) !important;
|
|
||||||
border-left: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr.margin,
|
|
||||||
.container hr {
|
|
||||||
margin: var(--pad-4) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.img_sizer {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
|
||||||
li,
|
|
||||||
span,
|
|
||||||
code {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-wrap: normal;
|
|
||||||
text-wrap: stable;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: var(--pad-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
margin: var(--pad-4) 0;
|
|
||||||
font-weight: 700;
|
|
||||||
width: -moz-max-content;
|
|
||||||
position: relative;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
margin: 2rem 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--color-link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color_block a {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.flush {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: inline;
|
|
||||||
max-width: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img_sizer img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
--size: 18px;
|
|
||||||
width: var(--size);
|
|
||||||
height: var(--size);
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
padding-left: 1rem;
|
|
||||||
border-left: solid 5px var(--color-green);
|
|
||||||
color: var(--color-green);
|
|
||||||
opacity: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
|
||||||
span {
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* extra */
|
|
||||||
@keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
animation: spin linear infinite 2s forwards running;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotateZ(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: rotateZ(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-end {
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* table */
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
table-layout: auto;
|
|
||||||
margin: var(--pad-4) 0;
|
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 0;
|
|
||||||
border: solid 1px var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
table td,
|
|
||||||
table th {
|
|
||||||
padding: var(--pad-2) var(--pad-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
table tr:not(thead *):nth-child(odd) {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
table thead th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* details */
|
|
||||||
details {
|
|
||||||
width: 100%;
|
|
||||||
margin: var(--pad-4) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
details summary {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
padding: var(--pad-2) var(--pad-4);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
details .content {
|
|
||||||
padding: var(--pad-4);
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dialog */
|
|
||||||
dialog {
|
|
||||||
background: var(--color-surface);
|
|
||||||
color: var(--color-text);
|
|
||||||
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
|
|
||||||
var(--color-shadow);
|
|
||||||
animation: fadein ease-in-out 1 0.25s forwards running;
|
|
||||||
max-width: 95%;
|
|
||||||
width: 30rem;
|
|
||||||
margin: auto;
|
|
||||||
padding: var(--pad-4);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--pad-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog::backdrop {
|
|
||||||
background: hsla(0, 0%, 0%, 25%);
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog:is(.dark *)::backdrop {
|
|
||||||
background: hsla(0, 0%, 100%, 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* menus */
|
|
||||||
menu {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu .button {
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu .button.active {
|
|
||||||
background: var(--color-super-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.col {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 25rem;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
|
||||||
(title
|
|
||||||
(text "Error - {{ name }}"))
|
|
||||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
|
||||||
(text "{% endblock %} {% block body %}")
|
|
||||||
(div
|
|
||||||
("class" "card")
|
|
||||||
(p (text "{{ error }}")))
|
|
||||||
(text "{% endblock %}")
|
|
|
@ -1,12 +0,0 @@
|
||||||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
|
||||||
(title
|
|
||||||
(text "{{ name }}"))
|
|
||||||
|
|
||||||
(meta ("property" "og:title") ("content" "{{ name }}"))
|
|
||||||
(meta ("property" "twitter:title") ("content" "{{ name }}"))
|
|
||||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
|
||||||
(text "{% endblock %} {% block body %}")
|
|
||||||
(div
|
|
||||||
("class" "card")
|
|
||||||
(h1 (text "{{ name }}")))
|
|
||||||
(text "{% endblock %}")
|
|
|
@ -1,75 +0,0 @@
|
||||||
(text "<!doctype html>")
|
|
||||||
(html
|
|
||||||
("lang" "en")
|
|
||||||
(head
|
|
||||||
(meta ("charset" "UTF-8"))
|
|
||||||
(meta ("name" "viewport") ("content" "width=device-width, initial-scale=1.0"))
|
|
||||||
(meta ("http-equiv" "X-UA-Compatible") ("content" "ie=edge"))
|
|
||||||
|
|
||||||
(link ("rel" "stylesheet") ("href" "https://repodelivery.trisua.com/tetratto/crates/app/src/public/css/utility.css"))
|
|
||||||
(link ("rel" "stylesheet") ("href" "/public/style.css?v={{ build_code }}"))
|
|
||||||
|
|
||||||
(style (text ":root { --color-primary: {{ theme_color }}; }"))
|
|
||||||
|
|
||||||
(meta ("name" "theme-color") ("content" "{{ theme_color }}"))
|
|
||||||
(meta ("property" "og:type") ("content" "website"))
|
|
||||||
(meta ("property" "og:site_name") ("content" "{{ name }}"))
|
|
||||||
|
|
||||||
(script ("src" "/public/app.js?v={{ build_code }}") ("defer"))
|
|
||||||
|
|
||||||
(text "{% block head %}{% endblock %}"))
|
|
||||||
|
|
||||||
(body
|
|
||||||
; nav
|
|
||||||
(nav
|
|
||||||
("class" "flex w_full justify_between gap_2 sticky")
|
|
||||||
(div
|
|
||||||
("class" "flex side")
|
|
||||||
(div
|
|
||||||
("class" "dropdown")
|
|
||||||
(button
|
|
||||||
("onclick" "open_dropdown(event)")
|
|
||||||
("exclude" "dropdown")
|
|
||||||
("class" "button camo fade")
|
|
||||||
(text "{{ icon \"menu\" }}"))
|
|
||||||
(div
|
|
||||||
("class" "inner left")
|
|
||||||
(a
|
|
||||||
("class" "button")
|
|
||||||
("href" "/")
|
|
||||||
(text "home"))
|
|
||||||
(a
|
|
||||||
("class" "button")
|
|
||||||
("href" "https://trisua.com/t/malachite")
|
|
||||||
(text "source"))
|
|
||||||
(text "{% block dropdown %}{% endblock %}")))
|
|
||||||
(a ("class" "button camo") ("href" "/") (b (text "{{ name }}"))))
|
|
||||||
|
|
||||||
(div
|
|
||||||
("class" "side flex")
|
|
||||||
(text "{% block nav_extras %}{% endblock %}")
|
|
||||||
|
|
||||||
; theme switches
|
|
||||||
(button
|
|
||||||
("class" "button camo fade")
|
|
||||||
("id" "switch_light")
|
|
||||||
("title" "Switch theme")
|
|
||||||
("onclick" "set_theme('Dark')")
|
|
||||||
(text "{{ icon \"sun\" }}"))
|
|
||||||
|
|
||||||
(button
|
|
||||||
("class" "button camo fade hidden")
|
|
||||||
("id" "switch_dark")
|
|
||||||
("title" "Switch theme")
|
|
||||||
("onclick" "set_theme('Light')")
|
|
||||||
(text "{{ icon \"moon\" }}"))))
|
|
||||||
|
|
||||||
; page
|
|
||||||
(article
|
|
||||||
("class" "content_container flex flex_col")
|
|
||||||
("id" "page")
|
|
||||||
(ul ("id" "messages"))
|
|
||||||
(text "{% block body %}{% endblock %}")
|
|
||||||
(div ("style" "min-height: 32px")))
|
|
||||||
|
|
||||||
(script (text "setTimeout(() => init_dropdowns(document.body), 150);"))))
|
|
19
crates/buckets-core/Cargo.toml
Normal file
19
crates/buckets-core/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "buckets-core"
|
||||||
|
description = "Buckets media upload types"
|
||||||
|
version = "1.0.1"
|
||||||
|
edition = "2024"
|
||||||
|
readme = "../../README.md"
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tetratto-core = "15.0.2"
|
||||||
|
tetratto-shared = "12.0.6"
|
||||||
|
pathbufd = "0.1.4"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.142"
|
||||||
|
toml = "0.9.4"
|
||||||
|
oiseau = { version = "0.1.2", default-features = false, features = ["postgres", "redis",] }
|
|
@ -4,30 +4,16 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The name of the site. Shown in the UI.
|
/// The directory files are stored in (relative to cwd).
|
||||||
#[serde(default = "default_name")]
|
#[serde(default = "default_directory")]
|
||||||
pub name: String,
|
pub directory: String,
|
||||||
/// The (CSS) theme color of the site. Shown in the UI.
|
|
||||||
#[serde(default = "default_theme_color")]
|
|
||||||
pub theme_color: String,
|
|
||||||
/// Real IP header (for reverse proxy).
|
|
||||||
#[serde(default = "default_real_ip_header")]
|
|
||||||
pub real_ip_header: String,
|
|
||||||
/// Database configuration.
|
/// Database configuration.
|
||||||
#[serde(default = "default_database")]
|
#[serde(default = "default_database")]
|
||||||
pub database: DatabaseConfig,
|
pub database: DatabaseConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_name() -> String {
|
fn default_directory() -> String {
|
||||||
"App".to_string()
|
"buckets".to_string()
|
||||||
}
|
|
||||||
|
|
||||||
fn default_theme_color() -> String {
|
|
||||||
"#6ee7b7".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_real_ip_header() -> String {
|
|
||||||
"CF-Connecting-IP".to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_database() -> DatabaseConfig {
|
fn default_database() -> DatabaseConfig {
|
||||||
|
@ -43,9 +29,7 @@ impl Configuration for Config {
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: default_name(),
|
directory: default_directory(),
|
||||||
theme_color: default_theme_color(),
|
|
||||||
real_ip_header: default_real_ip_header(),
|
|
||||||
database: default_database(),
|
database: default_database(),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
mod sql;
|
mod sql;
|
||||||
|
mod uploads;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use oiseau::{execute, postgres::DataManager as OiseauManager, postgres::Result as PgResult};
|
use oiseau::{execute, postgres::DataManager as OiseauManager, postgres::Result as PgResult};
|
||||||
use tetratto_core::model::{Error, Result};
|
use tetratto_core::model::{Error, Result};
|
||||||
|
|
||||||
pub const NAME_REGEX: &str = r"[^\w_\-\.,!]+";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DataManager(pub OiseauManager<Config>);
|
pub struct DataManager(pub OiseauManager<Config>);
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ impl DataManager {
|
||||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
// execute!(&conn, sql::CREATE_TABLE_ENTRIES).unwrap();
|
execute!(&conn, sql::CREATE_TABLE_UPLOADS).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
7
crates/buckets-core/src/database/sql/create_uploads.sql
Normal file
7
crates/buckets-core/src/database/sql/create_uploads.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS uploads (
|
||||||
|
id BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
created BIGINT NOT NULL,
|
||||||
|
owner BIGINT NOT NULL,
|
||||||
|
bucket TEXT NOT NULL,
|
||||||
|
metadata TEXT NOT NULL
|
||||||
|
)
|
1
crates/buckets-core/src/database/sql/mod.rs
Normal file
1
crates/buckets-core/src/database/sql/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub const CREATE_TABLE_UPLOADS: &str = include_str!("./create_uploads.sql");
|
167
crates/buckets-core/src/database/uploads.rs
Normal file
167
crates/buckets-core/src/database/uploads.rs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
use crate::{
|
||||||
|
DataManager,
|
||||||
|
model::{MediaUpload, UploadMetadata},
|
||||||
|
};
|
||||||
|
use oiseau::{PostgresRow, cache::Cache, execute, get, params, query_rows};
|
||||||
|
use tetratto_core::auto_method;
|
||||||
|
use tetratto_core::model::{Error, Result};
|
||||||
|
|
||||||
|
impl DataManager {
|
||||||
|
/// Get a [`MediaUpload`] from an SQL row.
|
||||||
|
pub(crate) fn get_upload_from_row(x: &PostgresRow) -> MediaUpload {
|
||||||
|
MediaUpload {
|
||||||
|
id: get!(x->0(i64)) as usize,
|
||||||
|
created: get!(x->1(i64)) as usize,
|
||||||
|
owner: get!(x->2(i64)) as usize,
|
||||||
|
bucket: get!(x->3(String)),
|
||||||
|
metadata: serde_json::from_str(&get!(x->4(String))).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(get_upload_by_id(usize as i64)@get_upload_from_row -> "SELECT * FROM uploads WHERE id = $1" --name="upload" --returns=MediaUpload --cache-key-tmpl="atto.upload:{}");
|
||||||
|
|
||||||
|
/// Get all uploads (paginated).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `batch` - the limit of items in each page
|
||||||
|
/// * `page` - the page number
|
||||||
|
pub async fn get_uploads(&self, batch: usize, page: usize) -> Result<Vec<MediaUpload>> {
|
||||||
|
let conn = match self.0.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = query_rows!(
|
||||||
|
&conn,
|
||||||
|
"SELECT * FROM uploads ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||||
|
&[&(batch as i64), &((page * batch) as i64)],
|
||||||
|
|x| { Self::get_upload_from_row(x) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("upload".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all uploads by their owner (paginated).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `owner` - the ID of the owner of the upload
|
||||||
|
/// * `batch` - the limit of items in each page
|
||||||
|
/// * `page` - the page number
|
||||||
|
pub async fn get_uploads_by_owner(
|
||||||
|
&self,
|
||||||
|
owner: usize,
|
||||||
|
batch: usize,
|
||||||
|
page: usize,
|
||||||
|
) -> Result<Vec<MediaUpload>> {
|
||||||
|
let conn = match self.0.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = query_rows!(
|
||||||
|
&conn,
|
||||||
|
"SELECT * FROM uploads WHERE owner = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
|
||||||
|
&[&(owner as i64), &(batch as i64), &((page * batch) as i64)],
|
||||||
|
|x| { Self::get_upload_from_row(x) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("upload".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all uploads by their owner.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `owner` - the ID of the owner of the upload
|
||||||
|
pub async fn get_uploads_by_owner_all(&self, owner: usize) -> Result<Vec<MediaUpload>> {
|
||||||
|
let conn = match self.0.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = query_rows!(
|
||||||
|
&conn,
|
||||||
|
"SELECT * FROM uploads WHERE owner = $1 ORDER BY created DESC",
|
||||||
|
&[&(owner as i64)],
|
||||||
|
|x| { Self::get_upload_from_row(x) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("upload".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new upload in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - a mock [`MediaUpload`] object to insert
|
||||||
|
pub async fn create_upload(&self, data: MediaUpload) -> Result<MediaUpload> {
|
||||||
|
let conn = match self.0.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
data.metadata.validate_kv()?;
|
||||||
|
|
||||||
|
let res = execute!(
|
||||||
|
&conn,
|
||||||
|
"INSERT INTO uploads VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
params![
|
||||||
|
&(data.id as i64),
|
||||||
|
&(data.created as i64),
|
||||||
|
&(data.owner as i64),
|
||||||
|
&data.bucket,
|
||||||
|
&serde_json::to_string(&data.metadata).unwrap().as_str(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_upload(&self, id: usize) -> Result<()> {
|
||||||
|
// if !user.permissions.check(FinePermission::MANAGE_UPLOADS) {
|
||||||
|
// return Err(Error::NotAllowed);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// delete file
|
||||||
|
// it's most important that the file gets off the file system first, even
|
||||||
|
// if there's an issue in the database
|
||||||
|
//
|
||||||
|
// the actual file takes up much more space than the database entry.
|
||||||
|
let upload = self.get_upload_by_id(id).await?;
|
||||||
|
upload.remove(&self.0.0.directory)?;
|
||||||
|
|
||||||
|
// delete from database
|
||||||
|
let conn = match self.0.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(&conn, "DELETE FROM uploads WHERE id = $1", &[&(id as i64)]);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.0.1.remove(format!("atto.upload:{}", id)).await;
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(update_upload_metadata(UploadMetadata) -> "UPDATE uploads SET metadata = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.upload:{}");
|
||||||
|
}
|
7
crates/buckets-core/src/lib.rs
Normal file
7
crates/buckets-core/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod model;
|
||||||
|
|
||||||
|
mod database;
|
||||||
|
pub use database::DataManager;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
pub use config::Config;
|
122
crates/buckets-core/src/model.rs
Normal file
122
crates/buckets-core/src/model.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use pathbufd::PathBufD;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::{exists, remove_file, write},
|
||||||
|
};
|
||||||
|
use tetratto_core::model::{Error, Result};
|
||||||
|
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum MediaType {
|
||||||
|
#[serde(alias = "image/webp")]
|
||||||
|
Webp,
|
||||||
|
#[serde(alias = "image/avif")]
|
||||||
|
Avif,
|
||||||
|
#[serde(alias = "image/png")]
|
||||||
|
Png,
|
||||||
|
#[serde(alias = "image/jpg")]
|
||||||
|
Jpg,
|
||||||
|
#[serde(alias = "image/gif")]
|
||||||
|
Gif,
|
||||||
|
#[serde(alias = "image/carpgraph")]
|
||||||
|
Carpgraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaType {
|
||||||
|
pub fn extension(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Webp => "webp",
|
||||||
|
Self::Avif => "avif",
|
||||||
|
Self::Png => "png",
|
||||||
|
Self::Jpg => "jpg",
|
||||||
|
Self::Gif => "gif",
|
||||||
|
Self::Carpgraph => "carpgraph",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mime(&self) -> String {
|
||||||
|
format!("image/{}", self.extension())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UploadMetadata {
|
||||||
|
pub what: MediaType,
|
||||||
|
#[serde(default)]
|
||||||
|
pub alt: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub kv: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UploadMetadata {
|
||||||
|
pub fn validate_kv(&self) -> Result<()> {
|
||||||
|
for x in &self.kv {
|
||||||
|
if x.0.len() > 32 {
|
||||||
|
return Err(Error::DataTooLong("key".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.1.len() > 128 {
|
||||||
|
return Err(Error::DataTooLong("value".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct MediaUpload {
|
||||||
|
pub id: usize,
|
||||||
|
pub created: usize,
|
||||||
|
pub owner: usize,
|
||||||
|
pub bucket: String,
|
||||||
|
pub metadata: UploadMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaUpload {
|
||||||
|
/// Create a new [`MediaUpload`].
|
||||||
|
pub fn new(what: MediaType, owner: usize, bucket: String) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
|
||||||
|
created: unix_epoch_timestamp(),
|
||||||
|
owner,
|
||||||
|
bucket,
|
||||||
|
metadata: UploadMetadata {
|
||||||
|
alt: String::new(),
|
||||||
|
what,
|
||||||
|
kv: HashMap::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path to the fs file for this upload.
|
||||||
|
pub fn path(&self, directory: &str) -> PathBufD {
|
||||||
|
PathBufD::current().extend(&[
|
||||||
|
directory,
|
||||||
|
&format!("{}.{}", self.id, self.metadata.what.extension()),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write to this upload in the file system.
|
||||||
|
pub fn write(&self, directory: &str, bytes: &[u8]) -> Result<()> {
|
||||||
|
match write(self.path(directory), bytes) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(Error::MiscError(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this upload in the file system.
|
||||||
|
pub fn remove(&self, directory: &str) -> Result<()> {
|
||||||
|
let path = self.path(directory);
|
||||||
|
|
||||||
|
if !exists(&path).unwrap() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match remove_file(path) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(Error::MiscError(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
crates/buckets/Cargo.toml
Normal file
25
crates/buckets/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "buckets"
|
||||||
|
description = "Buckets API service"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2024"
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tetratto-core = "15.0.2"
|
||||||
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
pathbufd = "0.1.4"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
tower-http = { version = "0.6.6", features = [
|
||||||
|
"trace",
|
||||||
|
"fs",
|
||||||
|
"catch-panic",
|
||||||
|
"set-header",
|
||||||
|
] }
|
||||||
|
axum = { version = "0.8.4", features = ["macros", "ws"] }
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
buckets-core = { path = "../buckets-core" }
|
74
crates/buckets/src/main.rs
Normal file
74
crates/buckets/src/main.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
use axum::{Extension, Router};
|
||||||
|
use buckets_core::{Config, DataManager};
|
||||||
|
use std::{env::var, net::SocketAddr, sync::Arc};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tower_http::{
|
||||||
|
catch_panic::CatchPanicLayer,
|
||||||
|
trace::{self, TraceLayer},
|
||||||
|
};
|
||||||
|
use tracing::{Level, info};
|
||||||
|
|
||||||
|
pub(crate) type State = Arc<RwLock<DataManager>>;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_dir_if_not_exists {
|
||||||
|
($dir_path:expr) => {
|
||||||
|
if !std::fs::exists(&$dir_path).unwrap() {
|
||||||
|
std::fs::create_dir($dir_path).unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() {
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_target(false)
|
||||||
|
.compact()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let port = match var("PORT") {
|
||||||
|
Ok(port) => port.parse::<u16>().expect("port should be a u16"),
|
||||||
|
Err(_) => 8020,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
let database = DataManager::new(Config::read())
|
||||||
|
.await
|
||||||
|
.expect("failed to connect to database");
|
||||||
|
database.init().await.expect("failed to init database");
|
||||||
|
create_dir_if_not_exists!(&database.0.0.directory);
|
||||||
|
|
||||||
|
// create app
|
||||||
|
let app = Router::new()
|
||||||
|
.merge(routes::routes())
|
||||||
|
.layer(Extension(Arc::new(RwLock::new(database))))
|
||||||
|
.layer(axum::extract::DefaultBodyLimit::max(
|
||||||
|
var("BODY_LIMIT")
|
||||||
|
.unwrap_or("8388608".to_string())
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
.layer(
|
||||||
|
TraceLayer::new_for_http()
|
||||||
|
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||||
|
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
||||||
|
)
|
||||||
|
.layer(CatchPanicLayer::new());
|
||||||
|
|
||||||
|
// ...
|
||||||
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("🪣 buckets.");
|
||||||
|
info!("listening on http://0.0.0.0:{}", port);
|
||||||
|
axum::serve(
|
||||||
|
listener,
|
||||||
|
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
94
crates/buckets/src/routes.rs
Normal file
94
crates/buckets/src/routes.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use crate::State;
|
||||||
|
use axum::{
|
||||||
|
Extension, Json, Router, body::Body, extract::Path, response::IntoResponse, routing::get,
|
||||||
|
};
|
||||||
|
use buckets_core::model::MediaType;
|
||||||
|
use pathbufd::PathBufD;
|
||||||
|
use std::{
|
||||||
|
fs::{File, exists},
|
||||||
|
io::Read,
|
||||||
|
};
|
||||||
|
use tetratto_core::model::{ApiReturn, Error, carp::CarpGraph};
|
||||||
|
|
||||||
|
pub fn routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/{bucket}/{id}", get(get_request))
|
||||||
|
.route("/{bucket}/{id}/json", get(get_json_request))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_image(path: PathBufD) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
for byte in File::open(path).unwrap().bytes() {
|
||||||
|
bytes.push(byte.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// api
|
||||||
|
pub async fn get_request(
|
||||||
|
Path((bucket, id)): Path<(String, usize)>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await);
|
||||||
|
|
||||||
|
let upload = match data.get_upload_by_id(id).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Json(e.into()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !upload.bucket.is_empty() && upload.bucket != bucket {
|
||||||
|
return Err(Json(ApiReturn {
|
||||||
|
ok: false,
|
||||||
|
message: Error::MiscError("Bucket mismatch".to_string()).to_string(),
|
||||||
|
payload: (),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
let path = upload.path(&data.0.0.directory);
|
||||||
|
|
||||||
|
if !exists(&path).unwrap() {
|
||||||
|
return Err(Json(Error::GeneralNotFound("file".to_string()).into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = read_image(path);
|
||||||
|
|
||||||
|
if upload.metadata.what == MediaType::Carpgraph {
|
||||||
|
// conver to svg and return
|
||||||
|
return Ok((
|
||||||
|
[("Content-Type", "image/svg+xml".to_string())],
|
||||||
|
Body::from(CarpGraph::from_bytes(bytes).to_svg()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
[("Content-Type", upload.metadata.what.mime())],
|
||||||
|
Body::from(bytes),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_json_request(
|
||||||
|
Path((bucket, id)): Path<(String, usize)>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await);
|
||||||
|
|
||||||
|
let upload = match data.get_upload_by_id(id).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !upload.bucket.is_empty() && upload.bucket != bucket {
|
||||||
|
return Json(Error::MiscError("Bucket mismatch".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: Some(upload),
|
||||||
|
})
|
||||||
|
}
|
3
justfile
3
justfile
|
@ -4,3 +4,6 @@ doc:
|
||||||
build:
|
build:
|
||||||
just doc
|
just doc
|
||||||
cargo build -r
|
cargo build -r
|
||||||
|
|
||||||
|
publish-core:
|
||||||
|
cargo publish --allow-dirty --package buckets-core
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
// pub const CREATE_TABLE_ENTRIES: &str = include_str!("./create_entries.sql");
|
|
136
src/main.rs
136
src/main.rs
|
@ -1,136 +0,0 @@
|
||||||
#![doc = include_str!("../README.md")]
|
|
||||||
mod config;
|
|
||||||
mod database;
|
|
||||||
mod markdown;
|
|
||||||
mod model;
|
|
||||||
mod routes;
|
|
||||||
|
|
||||||
use crate::database::DataManager;
|
|
||||||
use axum::{Extension, Router};
|
|
||||||
use config::Config;
|
|
||||||
use nanoneo::core::element::Render;
|
|
||||||
use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc};
|
|
||||||
use tera::{Tera, Value};
|
|
||||||
use tetratto_core::html;
|
|
||||||
use tetratto_shared::hash::salt;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tower_http::{
|
|
||||||
catch_panic::CatchPanicLayer,
|
|
||||||
trace::{self, TraceLayer},
|
|
||||||
};
|
|
||||||
use tracing::{Level, info};
|
|
||||||
|
|
||||||
pub(crate) type InnerState = (DataManager, Tera, String);
|
|
||||||
pub(crate) type State = Arc<RwLock<InnerState>>;
|
|
||||||
|
|
||||||
fn render_markdown(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
|
|
||||||
Ok(markdown::render_markdown(value.as_str().unwrap())
|
|
||||||
.replace("\\@", "@")
|
|
||||||
.replace("%5C@", "@")
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_script_tags(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
|
|
||||||
Ok(value
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace("</script>", "</script>")
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! create_dir_if_not_exists {
|
|
||||||
($dir_path:expr) => {
|
|
||||||
if !std::fs::exists(&$dir_path).unwrap() {
|
|
||||||
std::fs::create_dir($dir_path).unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
|
||||||
async fn main() {
|
|
||||||
dotenv::dotenv().ok();
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.with_target(false)
|
|
||||||
.compact()
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let port = match var("PORT") {
|
|
||||||
Ok(port) => port.parse::<u16>().expect("port should be a u16"),
|
|
||||||
Err(_) => 8020,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ...
|
|
||||||
let database = DataManager::new(Config::read())
|
|
||||||
.await
|
|
||||||
.expect("failed to connect to database");
|
|
||||||
database.init().await.expect("failed to init database");
|
|
||||||
|
|
||||||
// build lisp
|
|
||||||
create_dir_if_not_exists!("./templates_build");
|
|
||||||
create_dir_if_not_exists!("./icons");
|
|
||||||
|
|
||||||
for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") {
|
|
||||||
match x {
|
|
||||||
Ok(x) => std::fs::write(
|
|
||||||
x.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace("templates_src/", "templates_build/"),
|
|
||||||
html::pull_icons(
|
|
||||||
nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
|
|
||||||
.render(&mut HashMap::new()),
|
|
||||||
"./icons",
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
.expect("failed to write template"),
|
|
||||||
Err(e) => panic!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create docs dir
|
|
||||||
create_dir_if_not_exists!("./docs");
|
|
||||||
|
|
||||||
// ...
|
|
||||||
let mut tera = match Tera::new(&format!("./templates_build/**/*")) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
println!("{e}");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tera.register_filter("markdown", render_markdown);
|
|
||||||
tera.register_filter("remove_script_tags", remove_script_tags);
|
|
||||||
|
|
||||||
// create app
|
|
||||||
let app = Router::new()
|
|
||||||
.merge(routes::routes())
|
|
||||||
.layer(Extension(Arc::new(RwLock::new((database, tera, salt())))))
|
|
||||||
.layer(axum::extract::DefaultBodyLimit::max(
|
|
||||||
var("BODY_LIMIT")
|
|
||||||
.unwrap_or("8388608".to_string())
|
|
||||||
.parse::<usize>()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
.layer(
|
|
||||||
TraceLayer::new_for_http()
|
|
||||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
|
||||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
|
||||||
)
|
|
||||||
.layer(CatchPanicLayer::new());
|
|
||||||
|
|
||||||
// ...
|
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
info!("🪨 malachite.");
|
|
||||||
info!("listening on http://0.0.0.0:{}", port);
|
|
||||||
axum::serve(
|
|
||||||
listener,
|
|
||||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
pub fn render_markdown(input: &str) -> String {
|
|
||||||
let html = tetratto_shared::markdown::render_markdown_dirty(input);
|
|
||||||
|
|
||||||
let mut allowed_attributes = HashSet::new();
|
|
||||||
allowed_attributes.insert("id");
|
|
||||||
allowed_attributes.insert("class");
|
|
||||||
allowed_attributes.insert("ref");
|
|
||||||
allowed_attributes.insert("aria-label");
|
|
||||||
allowed_attributes.insert("lang");
|
|
||||||
allowed_attributes.insert("title");
|
|
||||||
allowed_attributes.insert("align");
|
|
||||||
allowed_attributes.insert("src");
|
|
||||||
allowed_attributes.insert("style");
|
|
||||||
|
|
||||||
tetratto_shared::markdown::clean_html(html, allowed_attributes)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
//! Base types matching SQL table structures.
|
|
|
@ -1,88 +0,0 @@
|
||||||
use crate::{State, config::Config};
|
|
||||||
use axum::{
|
|
||||||
Extension, Router,
|
|
||||||
extract::Path,
|
|
||||||
response::{Html, IntoResponse},
|
|
||||||
routing::{get, get_service},
|
|
||||||
};
|
|
||||||
use pathbufd::PathBufD;
|
|
||||||
use tera::Context;
|
|
||||||
use tetratto_core::model::Error;
|
|
||||||
|
|
||||||
pub fn routes() -> Router {
|
|
||||||
Router::new()
|
|
||||||
.nest_service(
|
|
||||||
"/public",
|
|
||||||
get_service(tower_http::services::ServeDir::new("./public")),
|
|
||||||
)
|
|
||||||
.fallback(not_found_request)
|
|
||||||
.route("/docs/{name}", get(view_doc_request))
|
|
||||||
// pages
|
|
||||||
.route("/", get(index_request))
|
|
||||||
// api
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_context(config: &Config, build_code: &str) -> Context {
|
|
||||||
let mut ctx = Context::new();
|
|
||||||
ctx.insert("name", &config.name);
|
|
||||||
ctx.insert("theme_color", &config.theme_color);
|
|
||||||
ctx.insert("build_code", &build_code);
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// pages
|
|
||||||
async fn not_found_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
|
||||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
|
||||||
|
|
||||||
let mut ctx = default_context(&data.0.0, &build_code);
|
|
||||||
ctx.insert(
|
|
||||||
"error",
|
|
||||||
&Error::GeneralNotFound("page".to_string()).to_string(),
|
|
||||||
);
|
|
||||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn index_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
|
||||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
|
||||||
Html(
|
|
||||||
tera.render("index.lisp", &default_context(&data.0.0, &build_code))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn view_doc_request(
|
|
||||||
Extension(data): Extension<State>,
|
|
||||||
Path(name): Path<String>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
|
||||||
let path = PathBufD::current().extend(&["docs", &format!("{name}.md")]);
|
|
||||||
|
|
||||||
if !std::fs::exists(&path).unwrap_or(false) {
|
|
||||||
let mut ctx = default_context(&data.0.0, &build_code);
|
|
||||||
ctx.insert(
|
|
||||||
"error",
|
|
||||||
&Error::GeneralNotFound("entry".to_string()).to_string(),
|
|
||||||
);
|
|
||||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = match std::fs::read_to_string(&path) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
let mut ctx = default_context(&data.0.0, &build_code);
|
|
||||||
ctx.insert("error", &Error::MiscError(e.to_string()).to_string());
|
|
||||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctx = default_context(&data.0.0, &build_code);
|
|
||||||
|
|
||||||
ctx.insert("text", &text);
|
|
||||||
ctx.insert("file_name", &name);
|
|
||||||
|
|
||||||
return Html(tera.render("doc.lisp", &ctx).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// api
|
|
||||||
// ...
|
|
Loading…
Add table
Add a link
Reference in a new issue