Compare commits
No commits in common. "4b3863e0d10e14acf13daacc2ea3e024c2d23968" and "163502505a1e62ef85b1abe8618955983b2d2bbd" have entirely different histories.
4b3863e0d1
...
163502505a
|
@ -7,10 +7,3 @@ coverage/
|
||||||
|
|
||||||
# Environment configuration
|
# Environment configuration
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# NodeJS files
|
|
||||||
node_modules/
|
|
||||||
pnpm-lock.yaml
|
|
||||||
|
|
||||||
# Default web build output directory
|
|
||||||
public/
|
|
||||||
|
|
|
@ -139,54 +139,6 @@ version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "askama"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139"
|
|
||||||
dependencies = [
|
|
||||||
"askama_derive",
|
|
||||||
"askama_escape",
|
|
||||||
"askama_shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "askama_derive"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71"
|
|
||||||
dependencies = [
|
|
||||||
"askama_shared",
|
|
||||||
"proc-macro2",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "askama_escape"
|
|
||||||
version = "0.10.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "askama_shared"
|
|
||||||
version = "0.12.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0"
|
|
||||||
dependencies = [
|
|
||||||
"askama_escape",
|
|
||||||
"humansize",
|
|
||||||
"mime",
|
|
||||||
"mime_guess",
|
|
||||||
"nom 7.1.1",
|
|
||||||
"num-traits",
|
|
||||||
"percent-encoding",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde",
|
|
||||||
"syn",
|
|
||||||
"toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-attributes"
|
name = "async-attributes"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -468,12 +420,6 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "beef"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
@ -573,21 +519,6 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "2.34.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
|
||||||
dependencies = [
|
|
||||||
"ansi_term",
|
|
||||||
"atty",
|
|
||||||
"bitflags",
|
|
||||||
"strsim 0.8.0",
|
|
||||||
"textwrap 0.11.0",
|
|
||||||
"unicode-width",
|
|
||||||
"vec_map",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.22"
|
version = "3.2.22"
|
||||||
|
@ -600,9 +531,9 @@ dependencies = [
|
||||||
"clap_lex 0.2.4",
|
"clap_lex 0.2.4",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap 0.15.1",
|
"textwrap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -616,7 +547,7 @@ dependencies = [
|
||||||
"clap_derive 4.0.10",
|
"clap_derive 4.0.10",
|
||||||
"clap_lex 0.3.0",
|
"clap_lex 0.3.0",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -664,12 +595,6 @@ dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "codemap"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -874,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core 0.9.3",
|
"parking_lot_core 0.9.3",
|
||||||
|
@ -1258,34 +1183,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "grass"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bc5bedc3dbd71dcdd41900e1f58e4d431fa69dd67c04ae1f86ae1a0339edd849"
|
|
||||||
dependencies = [
|
|
||||||
"beef",
|
|
||||||
"clap 2.34.0",
|
|
||||||
"codemap",
|
|
||||||
"indexmap",
|
|
||||||
"lasso",
|
|
||||||
"num-bigint",
|
|
||||||
"num-rational",
|
|
||||||
"num-traits",
|
|
||||||
"once_cell",
|
|
||||||
"phf 0.9.0",
|
|
||||||
"rand 0.8.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -1301,7 +1198,7 @@ version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1436,12 +1333,6 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humansize"
|
|
||||||
version = "1.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.50"
|
version = "0.1.50"
|
||||||
|
@ -1478,7 +1369,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1535,15 +1426,6 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lasso"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8647c8a01e5f7878eacb2c323c4c949fdb63773110f0686c7810769874b7e0a"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.11.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1724,18 +1606,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-rational"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-bigint",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -1892,22 +1762,11 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros 0.8.0",
|
"phf_macros",
|
||||||
"phf_shared 0.8.0",
|
"phf_shared 0.8.0",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37"
|
|
||||||
dependencies = [
|
|
||||||
"phf_macros 0.9.0",
|
|
||||||
"phf_shared 0.9.0",
|
|
||||||
"proc-macro-hack",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
@ -1947,16 +1806,6 @@ dependencies = [
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_generator"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082"
|
|
||||||
dependencies = [
|
|
||||||
"phf_shared 0.9.0",
|
|
||||||
"rand 0.8.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_generator"
|
name = "phf_generator"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -1981,20 +1830,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_macros"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86"
|
|
||||||
dependencies = [
|
|
||||||
"phf_generator 0.9.1",
|
|
||||||
"phf_shared 0.9.0",
|
|
||||||
"proc-macro-hack",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -2004,15 +1839,6 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_shared"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9"
|
|
||||||
dependencies = [
|
|
||||||
"siphasher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -2054,34 +1880,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"plotters-backend",
|
|
||||||
"plotters-svg",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-backend"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-svg"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
|
|
||||||
dependencies = [
|
|
||||||
"plotters-backend",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -3067,12 +2865,6 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -3140,15 +2932,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
|
@ -3205,14 +2988,11 @@ dependencies = [
|
||||||
name = "tildes-statistics"
|
name = "tildes-statistics"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
|
||||||
"async-std",
|
"async-std",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.0.10",
|
"clap 4.0.10",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"grass",
|
|
||||||
"plotters",
|
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
"surf",
|
"surf",
|
||||||
|
@ -3314,15 +3094,6 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.5.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.36"
|
version = "0.1.36"
|
||||||
|
@ -3510,12 +3281,6 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vec_map"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
@ -12,12 +12,10 @@ name = "tildes-statistics"
|
||||||
path = "source/main.rs"
|
path = "source/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = "0.11.1"
|
|
||||||
async-std = "1.12.0"
|
async-std = "1.12.0"
|
||||||
chrono = "0.4.22"
|
chrono = "0.4.22"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
dotenvy = "0.15.5"
|
dotenvy = "0.15.5"
|
||||||
grass = "0.11.2"
|
|
||||||
sea-orm-migration = "0.9.3"
|
sea-orm-migration = "0.9.3"
|
||||||
tracing = "0.1.36"
|
tracing = "0.1.36"
|
||||||
|
|
||||||
|
@ -25,11 +23,6 @@ tracing = "0.1.36"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
version = "4.0.10"
|
version = "4.0.10"
|
||||||
|
|
||||||
[dependencies.plotters]
|
|
||||||
default-features = false
|
|
||||||
features = ["line_series", "point_series", "svg_backend"]
|
|
||||||
version = "0.3.4"
|
|
||||||
|
|
||||||
[dependencies.sea-orm]
|
[dependencies.sea-orm]
|
||||||
features = ["macros", "mock", "runtime-async-std-rustls", "sqlx-postgres"]
|
features = ["macros", "mock", "runtime-async-std-rustls", "sqlx-postgres"]
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
|
|
@ -5,8 +5,8 @@ RUN USER=root cargo new --bin tildes-statistics
|
||||||
WORKDIR /tildes-statistics
|
WORKDIR /tildes-statistics
|
||||||
RUN mv src source
|
RUN mv src source
|
||||||
|
|
||||||
# Copy the configuration files and build in release, caching the dependencies.
|
# Copy the Cargo files and build in release, caching the dependencies.
|
||||||
COPY Cargo.lock Cargo.toml askama.toml .
|
COPY Cargo.* .
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
# Then copy our code. This way when only the source code changes, the
|
# Then copy our code. This way when only the source code changes, the
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[general]
|
|
||||||
dirs = ["source/templates"]
|
|
21
package.json
21
package.json
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"private": "true",
|
|
||||||
"scripts": {
|
|
||||||
"test": "stylelint 'source/**/*.scss'"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"modern-normalize": "^1.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"stylelint": "^14.12.1",
|
|
||||||
"stylelint-config-standard-scss": "^5.0.0"
|
|
||||||
},
|
|
||||||
"stylelint": {
|
|
||||||
"extends": [
|
|
||||||
"stylelint-config-standard-scss"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"string-quotes": "single"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
//! All code for drawing the [`plotters`] charts.
|
|
||||||
|
|
||||||
use {
|
|
||||||
async_std::{fs::create_dir_all, path::PathBuf},
|
|
||||||
color_eyre::Result,
|
|
||||||
plotters::prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::group_data::GroupDataModel;
|
|
||||||
|
|
||||||
const BACKGROUND_1: RGBColor = RGBColor(17, 17, 17);
|
|
||||||
const BACKGROUND_2: RGBColor = RGBColor(0, 0, 0);
|
|
||||||
const FOREGROUND: RGBColor = RGBColor(255, 255, 255);
|
|
||||||
const ACCENT_1: RGBColor = RGBColor(255, 0, 255);
|
|
||||||
|
|
||||||
/// The chart for the user count.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UserCountChart {
|
|
||||||
/// The groups to use for user counts.
|
|
||||||
pub groups: Vec<GroupDataModel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserCountChart {
|
|
||||||
/// Render the chart and write it to file.
|
|
||||||
pub async fn render(&self, parent: &PathBuf, group_name: &str) -> Result<()> {
|
|
||||||
let parent = parent.join("charts");
|
|
||||||
create_dir_all(&parent).await?;
|
|
||||||
|
|
||||||
let (mut datapoints, mut min_count, mut max_count) = (vec![], i64::MAX, 0);
|
|
||||||
|
|
||||||
for (index, group) in self.groups.iter().enumerate() {
|
|
||||||
datapoints.push(((index + 1) as isize, group.subscribers));
|
|
||||||
|
|
||||||
if group.subscribers > max_count {
|
|
||||||
max_count = group.subscribers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.subscribers < min_count {
|
|
||||||
min_count = group.subscribers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let datapoints_len = datapoints.len() as isize;
|
|
||||||
let min_count = min_count - 10;
|
|
||||||
let max_count = max_count + 10;
|
|
||||||
|
|
||||||
let path = parent.join("user-count.svg");
|
|
||||||
let chart_root = SVGBackend::new(&path, (1280, 720)).into_drawing_area();
|
|
||||||
chart_root.fill(&BACKGROUND_1)?;
|
|
||||||
|
|
||||||
let text_style =
|
|
||||||
|font_size: i32| ("sans-serif", font_size).into_font().color(&FOREGROUND);
|
|
||||||
|
|
||||||
let chart_root = chart_root
|
|
||||||
.margin(20, 20, 20, 20)
|
|
||||||
.titled("Tildes User Count", text_style(30))?;
|
|
||||||
|
|
||||||
chart_root.fill(&BACKGROUND_1)?;
|
|
||||||
|
|
||||||
let mut chart = ChartBuilder::on(&chart_root)
|
|
||||||
.caption(
|
|
||||||
format!("Using the {group_name} subscriber count."),
|
|
||||||
text_style(20),
|
|
||||||
)
|
|
||||||
.x_label_area_size(40)
|
|
||||||
.y_label_area_size(40)
|
|
||||||
.margin(10)
|
|
||||||
.build_cartesian_2d(0..(datapoints_len + 1), min_count..max_count)?;
|
|
||||||
|
|
||||||
chart
|
|
||||||
.configure_mesh()
|
|
||||||
.x_labels(datapoints.len() + 2)
|
|
||||||
.x_label_formatter(&|x| format!("{:0}", datapoints_len - x))
|
|
||||||
.x_desc("N days ago")
|
|
||||||
.y_labels(5)
|
|
||||||
.y_label_formatter(&|y| format!("{y:0}"))
|
|
||||||
.label_style(text_style(20))
|
|
||||||
.axis_style(&BACKGROUND_2)
|
|
||||||
.light_line_style(&BACKGROUND_2)
|
|
||||||
.bold_line_style(&BACKGROUND_1)
|
|
||||||
.draw()?;
|
|
||||||
|
|
||||||
chart
|
|
||||||
.draw_series(LineSeries::new(
|
|
||||||
datapoints.clone(),
|
|
||||||
ACCENT_1.stroke_width(2),
|
|
||||||
))?
|
|
||||||
.label("User Count")
|
|
||||||
.legend(|(x, y)| {
|
|
||||||
PathElement::new(vec![(x, y), (x + 20, y)], ACCENT_1.stroke_width(4))
|
|
||||||
});
|
|
||||||
|
|
||||||
chart.draw_series(PointSeries::of_element(
|
|
||||||
datapoints,
|
|
||||||
5,
|
|
||||||
&ACCENT_1,
|
|
||||||
&|(x, y), size, style| {
|
|
||||||
EmptyElement::at((x, y))
|
|
||||||
+ Circle::new((0, 0), size, style.filled())
|
|
||||||
+ Text::new(
|
|
||||||
{
|
|
||||||
if (x - 1) % 2 != 0 {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
format!("{:0}", y)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(-10, 15),
|
|
||||||
text_style(20),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! All CLI-related code.
|
//! All CLI-related code.
|
||||||
|
|
||||||
use {
|
use {
|
||||||
async_std::path::PathBuf,
|
|
||||||
chrono::NaiveDate,
|
chrono::NaiveDate,
|
||||||
clap::{Parser, Subcommand},
|
clap::{Parser, Subcommand},
|
||||||
};
|
};
|
||||||
|
@ -44,13 +43,6 @@ pub enum MainSubcommands {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: SnapshotSubcommands,
|
command: SnapshotSubcommands,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Website management.
|
|
||||||
Web {
|
|
||||||
/// Website management.
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: WebSubcommands,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Migrate subcommands.
|
/// Migrate subcommands.
|
||||||
|
@ -94,14 +86,3 @@ pub enum SnapshotSubcommands {
|
||||||
date: Option<NaiveDate>,
|
date: Option<NaiveDate>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Website subcommands.
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum WebSubcommands {
|
|
||||||
/// Build the website.
|
|
||||||
Build {
|
|
||||||
/// The output directory for the website files.
|
|
||||||
#[clap(short, long, default_value = "public")]
|
|
||||||
output: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
//! All logic for running the CLI.
|
//! All logic for running the CLI.
|
||||||
|
|
||||||
use {
|
use {
|
||||||
async_std::fs::create_dir_all, clap::Parser, color_eyre::Result,
|
clap::Parser, color_eyre::Result, sea_orm_migration::MigratorTrait,
|
||||||
sea_orm_migration::MigratorTrait, tracing::info,
|
tracing::info,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
charts::UserCountChart,
|
cli::{Cli, MainSubcommands, MigrateSubcommands, SnapshotSubcommands},
|
||||||
cli::{
|
group_data::get_all_by_snapshot,
|
||||||
Cli, MainSubcommands, MigrateSubcommands, SnapshotSubcommands,
|
|
||||||
WebSubcommands,
|
|
||||||
},
|
|
||||||
group_data::GroupDataModel,
|
|
||||||
migrations::Migrator,
|
migrations::Migrator,
|
||||||
scss::generate_css,
|
snapshots::{self, get_by_date},
|
||||||
snapshots::SnapshotModel,
|
|
||||||
templates::HomeTemplate,
|
|
||||||
utilities::{create_db, today},
|
utilities::{create_db, today},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,20 +43,18 @@ pub async fn run() -> Result<()> {
|
||||||
command: snapshot_command,
|
command: snapshot_command,
|
||||||
} => match snapshot_command {
|
} => match snapshot_command {
|
||||||
SnapshotSubcommands::Create { force } => {
|
SnapshotSubcommands::Create { force } => {
|
||||||
SnapshotModel::create(&db, force).await?;
|
snapshots::create(&db, force).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
SnapshotSubcommands::List {} => {
|
SnapshotSubcommands::List {} => {
|
||||||
for snapshot in SnapshotModel::get_all(&db).await? {
|
for snapshot in snapshots::get_all(&db).await? {
|
||||||
info!("Snapshot {snapshot:?}")
|
info!("Snapshot {snapshot:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SnapshotSubcommands::Show { date } => {
|
SnapshotSubcommands::Show { date } => {
|
||||||
let date = date.unwrap_or_else(today);
|
let date = date.unwrap_or_else(today);
|
||||||
let snapshot = if let Some(snapshot) =
|
let snapshot = if let Some(snapshot) = get_by_date(&db, date).await? {
|
||||||
SnapshotModel::get_by_date(&db, date).await?
|
|
||||||
{
|
|
||||||
info!("Snapshot {snapshot:?}");
|
info!("Snapshot {snapshot:?}");
|
||||||
snapshot
|
snapshot
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,8 +62,8 @@ pub async fn run() -> Result<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
for group in GroupDataModel::get_all_by_snapshot(&db, &snapshot).await?
|
let groups = get_all_by_snapshot(&db, &snapshot).await?;
|
||||||
{
|
for group in groups {
|
||||||
info!(
|
info!(
|
||||||
id = group.id,
|
id = group.id,
|
||||||
name = group.name,
|
name = group.name,
|
||||||
|
@ -80,35 +72,6 @@ pub async fn run() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
MainSubcommands::Web {
|
|
||||||
command: web_command,
|
|
||||||
} => match web_command {
|
|
||||||
WebSubcommands::Build { output } => {
|
|
||||||
let user_count_group =
|
|
||||||
if let Some(snapshot) = SnapshotModel::get_most_recent(&db).await? {
|
|
||||||
GroupDataModel::get_highest_subscribers(&db, &snapshot).await?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
create_dir_all(&output).await?;
|
|
||||||
HomeTemplate::new(
|
|
||||||
user_count_group.as_ref().map(|group| group.subscribers),
|
|
||||||
)
|
|
||||||
.render_to_file(&output)
|
|
||||||
.await?;
|
|
||||||
generate_css(&output).await?;
|
|
||||||
|
|
||||||
if let Some(group) = user_count_group {
|
|
||||||
let groups =
|
|
||||||
GroupDataModel::get_n_most_recent(&db, 30, &group.name).await?;
|
|
||||||
UserCountChart { groups }
|
|
||||||
.render(&output, &group.name)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,55 +1,14 @@
|
||||||
//! All logic for group datas.
|
//! All logic for group datas.
|
||||||
|
|
||||||
use {
|
use {color_eyre::Result, sea_orm::prelude::*};
|
||||||
color_eyre::Result,
|
|
||||||
sea_orm::{prelude::*, QueryOrder, QuerySelect},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use crate::{
|
use crate::entities::{group_data, snapshot};
|
||||||
entities::group_data::{
|
|
||||||
ActiveModel as GroupDataActiveModel, Column as GroupDataColumn,
|
|
||||||
Entity as GroupDataEntity, Model as GroupDataModel,
|
|
||||||
},
|
|
||||||
snapshots::SnapshotModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl GroupDataModel {
|
/// Get all group datas from a given snapshot.
|
||||||
/// Get all group datas from a given snapshot.
|
pub async fn get_all_by_snapshot(
|
||||||
pub async fn get_all_by_snapshot(
|
db: &DatabaseConnection,
|
||||||
db: &DatabaseConnection,
|
snapshot: &snapshot::Model,
|
||||||
snapshot: &SnapshotModel,
|
) -> Result<Vec<group_data::Model>> {
|
||||||
) -> Result<Vec<Self>> {
|
let groups = snapshot.find_related(group_data::Entity).all(db).await?;
|
||||||
let groups = snapshot.find_related(GroupDataEntity).all(db).await?;
|
Ok(groups)
|
||||||
Ok(groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the group with the highest subscriber count from a given snapshot.
|
|
||||||
pub async fn get_highest_subscribers(
|
|
||||||
db: &DatabaseConnection,
|
|
||||||
snapshot: &SnapshotModel,
|
|
||||||
) -> Result<Option<Self>> {
|
|
||||||
let group = snapshot
|
|
||||||
.find_related(GroupDataEntity)
|
|
||||||
.order_by_desc(GroupDataColumn::Subscribers)
|
|
||||||
.one(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the N most recently saved group datas from a given group name.
|
|
||||||
pub async fn get_n_most_recent(
|
|
||||||
db: &DatabaseConnection,
|
|
||||||
amount: u64,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Vec<Self>> {
|
|
||||||
let groups = GroupDataEntity::find()
|
|
||||||
.order_by_desc(GroupDataColumn::SnapshotId)
|
|
||||||
.filter(GroupDataColumn::Name.eq(name))
|
|
||||||
.limit(amount)
|
|
||||||
.all(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(groups)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,10 @@ use {
|
||||||
tracing_subscriber::filter::{EnvFilter, LevelFilter},
|
tracing_subscriber::filter::{EnvFilter, LevelFilter},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod charts;
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod group_data;
|
pub mod group_data;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod scss;
|
|
||||||
pub mod snapshots;
|
pub mod snapshots;
|
||||||
pub mod templates;
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
|
|
||||||
/// The entities code is auto-generated using `sea-orm-cli`. With a database
|
/// The entities code is auto-generated using `sea-orm-cli`. With a database
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
--small-spacing: 4px;
|
|
||||||
--medium-spacing: 8px;
|
|
||||||
--large-spacing: 16px;
|
|
||||||
--background-1: #222;
|
|
||||||
--background-2: #111;
|
|
||||||
--foreground-1: #fff;
|
|
||||||
--anchor-1: #f0f;
|
|
||||||
--anchor-2: #000;
|
|
||||||
|
|
||||||
background-color: var(--background-1);
|
|
||||||
color: var(--foreground-1);
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
a:visited {
|
|
||||||
color: var(--anchor-1);
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--anchor-1);
|
|
||||||
color: var(--anchor-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
p,
|
|
||||||
ol,
|
|
||||||
li {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.underline {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
@mixin responsive-container($breakpoint) {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: $breakpoint;
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header,
|
|
||||||
.page-main,
|
|
||||||
.page-footer {
|
|
||||||
@include responsive-container(1200px);
|
|
||||||
|
|
||||||
margin-bottom: var(--large-spacing);
|
|
||||||
padding: var(--large-spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
border-bottom: 4px solid var(--background-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-main {
|
|
||||||
h2 {
|
|
||||||
margin-bottom: var(--medium-spacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-footer {
|
|
||||||
background-color: var(--background-2);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--large-spacing);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
//! All SCSS files.
|
|
||||||
|
|
||||||
use {
|
|
||||||
async_std::{
|
|
||||||
fs::{create_dir_all, write},
|
|
||||||
path::PathBuf,
|
|
||||||
},
|
|
||||||
color_eyre::{eyre::Context, Result},
|
|
||||||
};
|
|
||||||
|
|
||||||
const MODERN_NORMALIZE_CSS: &str =
|
|
||||||
include_str!("../../node_modules/modern-normalize/modern-normalize.css");
|
|
||||||
|
|
||||||
/// Generate the CSS files and write them.
|
|
||||||
pub async fn generate_css(parent: &PathBuf) -> Result<()> {
|
|
||||||
let parent = parent.join("css");
|
|
||||||
create_dir_all(&parent).await?;
|
|
||||||
|
|
||||||
let render = |scss: &str| -> Result<String> {
|
|
||||||
grass::from_string(scss.to_string(), &grass::Options::default())
|
|
||||||
.wrap_err("Failed SCSS render")
|
|
||||||
};
|
|
||||||
|
|
||||||
let css_to_create = vec![
|
|
||||||
("modern-normalize.css", MODERN_NORMALIZE_CSS, false),
|
|
||||||
("common.css", include_str!("common.scss"), true),
|
|
||||||
("index.css", include_str!("index.scss"), true),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (file, css, is_scss) in css_to_create {
|
|
||||||
let path = parent.join(file);
|
|
||||||
if is_scss {
|
|
||||||
write(path, render(css)?).await?;
|
|
||||||
} else {
|
|
||||||
write(path, css).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -8,71 +8,69 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
group_data::{GroupDataActiveModel, GroupDataEntity},
|
entities::{group_data, snapshot},
|
||||||
snapshots::{SnapshotActiveModel, SnapshotModel},
|
snapshots::get_by_date,
|
||||||
utilities::{create_http_client, download_html, today},
|
utilities::{create_http_client, download_html, today},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl SnapshotModel {
|
/// Create a snapshot for today.
|
||||||
/// Create a snapshot for today.
|
pub async fn create(db: &DatabaseConnection, force: bool) -> Result<()> {
|
||||||
pub async fn create(db: &DatabaseConnection, force: bool) -> Result<()> {
|
let snapshot_date = today();
|
||||||
let snapshot_date = today();
|
match (force, get_by_date(db, snapshot_date).await?) {
|
||||||
match (force, Self::get_by_date(db, snapshot_date).await?) {
|
(true, Some(existing)) => {
|
||||||
(true, Some(existing)) => {
|
info!("Removing existing snapshot {:?}", existing);
|
||||||
info!("Removing existing snapshot {:?}", existing);
|
existing.delete(db).await?;
|
||||||
existing.delete(db).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
(false, Some(existing)) => {
|
|
||||||
info!("Snapshot for today already exists");
|
|
||||||
info!("Use --force to override snapshot {:?}", existing);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
(_, None) => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
let transaction = db.begin().await?;
|
|
||||||
let snapshot = SnapshotActiveModel {
|
|
||||||
date: Set(snapshot_date),
|
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
.insert(&transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Scraping data for snapshot {:?}", snapshot);
|
(false, Some(existing)) => {
|
||||||
|
info!("Snapshot for today already exists");
|
||||||
|
info!("Use --force to override snapshot {:?}", existing);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let http = create_http_client()?;
|
(_, None) => (),
|
||||||
let group_list = GroupList::from_html(
|
};
|
||||||
&download_html(&http, "https://tildes.net/groups").await?,
|
|
||||||
|
let transaction = db.begin().await?;
|
||||||
|
let snapshot = snapshot::ActiveModel {
|
||||||
|
date: Set(snapshot_date),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.insert(&transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Scraping data for snapshot {:?}", snapshot);
|
||||||
|
|
||||||
|
let http = create_http_client()?;
|
||||||
|
let group_list = GroupList::from_html(
|
||||||
|
&download_html(&http, "https://tildes.net/groups").await?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut groups_to_insert = vec![];
|
||||||
|
|
||||||
|
for summary in group_list.summaries {
|
||||||
|
debug!(summary = ?summary);
|
||||||
|
let group = Group::from_html(
|
||||||
|
&download_html(&http, format!("https://tildes.net/{}", summary.name))
|
||||||
|
.await?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut groups_to_insert = vec![];
|
debug!(group = ?group);
|
||||||
|
groups_to_insert.push(group_data::ActiveModel {
|
||||||
for summary in group_list.summaries {
|
description: Set(group.description),
|
||||||
debug!(summary = ?summary);
|
name: Set(group.name),
|
||||||
let group = Group::from_html(
|
snapshot_id: Set(snapshot.id),
|
||||||
&download_html(&http, format!("https://tildes.net/{}", summary.name))
|
subscribers: Set(group.subscribers.into()),
|
||||||
.await?,
|
..Default::default()
|
||||||
)?;
|
});
|
||||||
|
|
||||||
debug!(group = ?group);
|
|
||||||
groups_to_insert.push(GroupDataActiveModel {
|
|
||||||
description: Set(group.description),
|
|
||||||
name: Set(group.name),
|
|
||||||
snapshot_id: Set(snapshot.id),
|
|
||||||
subscribers: Set(group.subscribers.into()),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Inserting {} groups", groups_to_insert.len());
|
|
||||||
GroupDataEntity::insert_many(groups_to_insert)
|
|
||||||
.exec(&transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Inserting {} groups", groups_to_insert.len());
|
||||||
|
group_data::Entity::insert_many(groups_to_insert)
|
||||||
|
.exec(&transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,44 +5,29 @@ use {
|
||||||
sea_orm::{prelude::*, QueryOrder},
|
sea_orm::{prelude::*, QueryOrder},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::entities::snapshot;
|
||||||
|
|
||||||
mod create;
|
mod create;
|
||||||
|
|
||||||
pub use crate::entities::snapshot::{
|
pub use create::create;
|
||||||
ActiveModel as SnapshotActiveModel, Column as SnapshotColumn,
|
|
||||||
Entity as SnapshotEntity, Model as SnapshotModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl SnapshotModel {
|
/// Get a snapshot for a given date.
|
||||||
/// Get a snapshot for a given date.
|
pub async fn get_by_date(
|
||||||
pub async fn get_by_date(
|
db: &DatabaseConnection,
|
||||||
db: &DatabaseConnection,
|
date: ChronoDate,
|
||||||
date: ChronoDate,
|
) -> Result<Option<snapshot::Model>> {
|
||||||
) -> Result<Option<Self>> {
|
let existing = snapshot::Entity::find()
|
||||||
let existing = SnapshotEntity::find()
|
.filter(snapshot::Column::Date.eq(date))
|
||||||
.filter(SnapshotColumn::Date.eq(date))
|
.order_by_desc(snapshot::Column::Date)
|
||||||
.order_by_desc(SnapshotColumn::Date)
|
.one(db)
|
||||||
.one(db)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(existing)
|
Ok(existing)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all snapshots.
|
/// Get all snapshots.
|
||||||
pub async fn get_all(db: &DatabaseConnection) -> Result<Vec<Self>> {
|
pub async fn get_all(db: &DatabaseConnection) -> Result<Vec<snapshot::Model>> {
|
||||||
let snapshots = SnapshotEntity::find().all(db).await?;
|
let snapshots = snapshot::Entity::find().all(db).await?;
|
||||||
|
|
||||||
Ok(snapshots)
|
Ok(snapshots)
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the most recent snapshot.
|
|
||||||
pub async fn get_most_recent(
|
|
||||||
db: &DatabaseConnection,
|
|
||||||
) -> Result<Option<Self>> {
|
|
||||||
let snapshot = SnapshotEntity::find()
|
|
||||||
.order_by_desc(SnapshotColumn::Date)
|
|
||||||
.one(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(snapshot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ page_title }}</title>
|
|
||||||
<link rel="stylesheet" href="/css/modern-normalize.css">
|
|
||||||
<link rel="stylesheet" href="/css/common.css">
|
|
||||||
{% block head %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,43 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<link rel="stylesheet" href="/css/index.css">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<header class="page-header">
|
|
||||||
<h1>Tildes Statistics</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="page-main">
|
|
||||||
<h2>General</h2>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
There are currently an
|
|
||||||
<abbr title="Based on the group with the highest subscriber count.">estimated</abbr>
|
|
||||||
<span class="underline">{{ user_count }}</span>
|
|
||||||
registered users on Tildes.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<img src="/charts/user-count.svg" alt="User Count Chart">
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="page-footer">
|
|
||||||
<p>
|
|
||||||
Last generated on
|
|
||||||
<time class="underline" datetime="{{ today }}">{{- today -}}</time>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
© Code
|
|
||||||
<a href="https://git.bauke.xyz/Bauke/tildes-statistics">AGPL-3.0-or-later</a>,
|
|
||||||
charts & data
|
|
||||||
<a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="bold">
|
|
||||||
Consider joining <a href="https://tildes.net">Tildes</a>, a non-profit
|
|
||||||
community site driven by its users' interests.
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
{% endblock %}
|
|
|
@ -1,44 +0,0 @@
|
||||||
//! All HTML templates.
|
|
||||||
|
|
||||||
use {
|
|
||||||
askama::Template,
|
|
||||||
async_std::{fs::write, path::PathBuf},
|
|
||||||
chrono::NaiveDate,
|
|
||||||
color_eyre::Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::utilities::today;
|
|
||||||
|
|
||||||
/// The template for the home page.
|
|
||||||
#[derive(Template)]
|
|
||||||
#[template(path = "index.html")]
|
|
||||||
pub struct HomeTemplate {
|
|
||||||
/// The string for the `<title>` element.
|
|
||||||
pub page_title: String,
|
|
||||||
|
|
||||||
/// The date of today's snapshot.
|
|
||||||
pub today: NaiveDate,
|
|
||||||
|
|
||||||
/// The user count from the group with the most subscribers.
|
|
||||||
pub user_count: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HomeTemplate {
|
|
||||||
/// Create a new [`HomeTemplate`].
|
|
||||||
pub fn new(user_count: Option<i64>) -> Self {
|
|
||||||
Self {
|
|
||||||
page_title: "Tildes Statistics".to_string(),
|
|
||||||
today: today(),
|
|
||||||
user_count: user_count
|
|
||||||
.map(|n| n.to_string())
|
|
||||||
.unwrap_or_else(|| "unknown".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render the template and write it to file.
|
|
||||||
pub async fn render_to_file(&self, parent: &PathBuf) -> Result<()> {
|
|
||||||
write(parent.join("index.html"), self.render()?).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue