all good
This commit is contained in:
parent
47d89f6fad
commit
017902b8e6
82 changed files with 331466 additions and 54841 deletions
456
server-rs/Cargo.lock
generated
456
server-rs/Cargo.lock
generated
|
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
|
|
@ -683,6 +692,21 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.1.1"
|
||||
|
|
@ -772,6 +796,15 @@ dependencies = [
|
|||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boxcar"
|
||||
version = "0.2.14"
|
||||
|
|
@ -1295,6 +1328,16 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2"
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.6.1"
|
||||
|
|
@ -1338,6 +1381,16 @@ dependencies = [
|
|||
"ctutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
|
@ -1532,6 +1585,18 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
|
|
@ -1773,6 +1838,12 @@ dependencies = [
|
|||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
|
|
@ -1947,6 +2018,17 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
|
|
@ -2675,6 +2757,18 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "now"
|
||||
version = "0.1.3"
|
||||
|
|
@ -2738,6 +2832,36 @@ dependencies = [
|
|||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
|
|
@ -2745,6 +2869,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2757,6 +2947,60 @@ dependencies = [
|
|||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-ui-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-core-text",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
|
|
@ -2831,6 +3075,22 @@ dependencies = [
|
|||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"log",
|
||||
"nix",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"serde",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.5.2"
|
||||
|
|
@ -2926,6 +3186,26 @@ dependencies = [
|
|||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
|
|
@ -3616,6 +3896,7 @@ dependencies = [
|
|||
"reqwest 0.13.3",
|
||||
"rust_xlsxwriter",
|
||||
"rustc-hash",
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
|
|
@ -3935,6 +4216,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.14",
|
||||
|
|
@ -4111,6 +4393,12 @@ dependencies = [
|
|||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
|
|
@ -4346,6 +4634,130 @@ version = "1.0.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "sentry"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"httpdate",
|
||||
"reqwest 0.12.28",
|
||||
"rustls 0.23.40",
|
||||
"sentry-backtrace",
|
||||
"sentry-contexts",
|
||||
"sentry-core",
|
||||
"sentry-debug-images",
|
||||
"sentry-panic",
|
||||
"sentry-tower",
|
||||
"sentry-tracing",
|
||||
"tokio",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-backtrace"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"regex",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-contexts"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"libc",
|
||||
"os_info",
|
||||
"rustc_version",
|
||||
"sentry-core",
|
||||
"uname",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-core"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06"
|
||||
dependencies = [
|
||||
"rand 0.9.4",
|
||||
"sentry-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-debug-images"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "002561e49ea3a9de316e2efadc40fae553921b8ff41448f02ea85fd135a778d6"
|
||||
dependencies = [
|
||||
"findshlibs",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-panic"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-tower"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56aebe376310840b49dad4cca55c7b32d9abdc14946cd071d4158ecb149b63a4"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"http 1.4.0",
|
||||
"pin-project",
|
||||
"sentry-core",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-tracing"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-types"
|
||||
version = "0.46.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"hex",
|
||||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -5147,6 +5559,15 @@ version = "1.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "uname"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
|
|
@ -5207,6 +5628,34 @@ version = "0.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls 0.23.40",
|
||||
"rustls-pki-types",
|
||||
"ureq-proto",
|
||||
"utf8-zero",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"http 1.4.0",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
|
|
@ -5217,6 +5666,7 @@ dependencies = [
|
|||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5225,6 +5675,12 @@ version = "2.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-zero"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ sha2 = "0.11"
|
|||
hex = "0.4"
|
||||
tower = { version = "0.5", features = ["limit"] }
|
||||
libc = "0.2"
|
||||
sentry = { version = "0.46.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "reqwest", "rustls", "tracing", "tower-http", "tower-axum-matched-path"] }
|
||||
|
||||
[lints.clippy]
|
||||
min_ident_chars = "warn"
|
||||
|
|
|
|||
80
server-rs/src/bugsink.rs
Normal file
80
server-rs/src/bugsink.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BackendConfig {
|
||||
pub dsn: Option<String>,
|
||||
pub environment: Option<String>,
|
||||
pub release: Option<String>,
|
||||
pub send_default_pii: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FrontendConfig {
|
||||
pub dsn: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub environment: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub release: Option<String>,
|
||||
pub send_default_pii: bool,
|
||||
}
|
||||
|
||||
pub fn env_nonempty(name: &str) -> Option<String> {
|
||||
std::env::var(name).ok().and_then(nonempty)
|
||||
}
|
||||
|
||||
pub fn nonempty(value: String) -> Option<String> {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_owned())
|
||||
}
|
||||
|
||||
pub fn default_release() -> String {
|
||||
format!("{}@{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
pub fn init_backend(config: &BackendConfig) -> Option<sentry::ClientInitGuard> {
|
||||
let dsn = config.dsn.clone().and_then(nonempty)?;
|
||||
|
||||
let dsn = match dsn.parse::<sentry::types::Dsn>() {
|
||||
Ok(dsn) => dsn,
|
||||
Err(err) => {
|
||||
eprintln!("Ignoring invalid BUGSINK_DSN: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(sentry::init(sentry::ClientOptions {
|
||||
dsn: Some(dsn),
|
||||
environment: config
|
||||
.environment
|
||||
.clone()
|
||||
.and_then(nonempty)
|
||||
.map(Cow::Owned),
|
||||
release: Some(Cow::Owned(
|
||||
config
|
||||
.release
|
||||
.clone()
|
||||
.and_then(nonempty)
|
||||
.unwrap_or_else(default_release),
|
||||
)),
|
||||
send_default_pii: config.send_default_pii,
|
||||
traces_sample_rate: 0.0,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn frontend_config(
|
||||
dsn: Option<String>,
|
||||
environment: Option<String>,
|
||||
release: Option<String>,
|
||||
send_default_pii: bool,
|
||||
) -> Option<FrontendConfig> {
|
||||
dsn.and_then(nonempty).map(|dsn| FrontendConfig {
|
||||
dsn,
|
||||
environment: environment.and_then(nonempty),
|
||||
release: release.and_then(nonempty),
|
||||
send_default_pii,
|
||||
})
|
||||
}
|
||||
|
|
@ -21,6 +21,16 @@ pub struct PlaceData {
|
|||
pub travel_destination: Vec<bool>,
|
||||
}
|
||||
|
||||
pub(super) struct CityCandidate<'a> {
|
||||
pub(super) name: &'a str,
|
||||
pub(super) lat: f32,
|
||||
pub(super) lon: f32,
|
||||
}
|
||||
|
||||
const PARENT_CITY_MAX_DIST_SQ: f32 = 0.81;
|
||||
const LONDON_DISPLAY_MAX_DEGREES: f32 = 30.0 / 111.0;
|
||||
const LONDON_DISPLAY_MAX_DIST_SQ: f32 = LONDON_DISPLAY_MAX_DEGREES * LONDON_DISPLAY_MAX_DEGREES;
|
||||
|
||||
fn type_rank(place_type: &str) -> u8 {
|
||||
match place_type {
|
||||
"city" => 0,
|
||||
|
|
@ -37,6 +47,53 @@ pub fn is_travel_destination_type(place_type: &str) -> bool {
|
|||
matches!(place_type, "city" | "station" | "university")
|
||||
}
|
||||
|
||||
fn distance_sq(lat: f32, lon: f32, city: &CityCandidate<'_>) -> f32 {
|
||||
let cos_lat = lat.to_radians().cos();
|
||||
let dlat = city.lat - lat;
|
||||
let dlon = (city.lon - lon) * cos_lat;
|
||||
dlat * dlat + dlon * dlon
|
||||
}
|
||||
|
||||
fn is_london_city_name(name: &str) -> bool {
|
||||
matches!(name, "London" | "Westminster" | "City of London")
|
||||
}
|
||||
|
||||
pub(super) fn nearest_display_city<'a>(
|
||||
lat: f32,
|
||||
lon: f32,
|
||||
cities: &'a [CityCandidate<'a>],
|
||||
) -> Option<&'a str> {
|
||||
let mut best_dist_sq = f32::MAX;
|
||||
let mut best_city: Option<&CityCandidate<'_>> = None;
|
||||
let mut london_dist_sq: Option<f32> = None;
|
||||
|
||||
for city in cities {
|
||||
let dist_sq = distance_sq(lat, lon, city);
|
||||
if city.name == "London" {
|
||||
london_dist_sq = Some(dist_sq);
|
||||
}
|
||||
if dist_sq < best_dist_sq {
|
||||
best_dist_sq = dist_sq;
|
||||
best_city = Some(city);
|
||||
}
|
||||
}
|
||||
|
||||
let best_city = best_city?;
|
||||
if best_dist_sq >= PARENT_CITY_MAX_DIST_SQ {
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_london_city_name(best_city.name) {
|
||||
if london_dist_sq.is_some_and(|dist_sq| dist_sq < LONDON_DISPLAY_MAX_DIST_SQ) {
|
||||
Some("London")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(best_city.name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_search_text(text: &str) -> String {
|
||||
let mut result = String::with_capacity(text.len());
|
||||
let mut last_was_space = true;
|
||||
|
|
@ -182,6 +239,25 @@ fn extract_bool_col(df: &DataFrame, name: &str) -> anyhow::Result<Vec<bool>> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn extract_optional_str_col(
|
||||
df: &DataFrame,
|
||||
name: &str,
|
||||
) -> anyhow::Result<Option<Vec<Option<String>>>> {
|
||||
let column = match df.column(name) {
|
||||
Ok(column) => column,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
let string_column = column
|
||||
.str()
|
||||
.with_context(|| format!("Column '{name}' is not a string column"))?;
|
||||
Ok(Some(
|
||||
string_column
|
||||
.into_iter()
|
||||
.map(|value| value.map(ToString::to_string))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
impl PlaceData {
|
||||
pub fn load(parquet_path: &Path) -> anyhow::Result<Self> {
|
||||
super::run_polars_io(|| Self::load_inner(parquet_path))
|
||||
|
|
@ -227,6 +303,7 @@ impl PlaceData {
|
|||
.map(|place_type| is_travel_destination_type(place_type))
|
||||
.collect()
|
||||
};
|
||||
let display_city_override = extract_optional_str_col(&df, "display_city")?;
|
||||
|
||||
// Precompute nearest city for each non-city place
|
||||
let city_indices: Vec<usize> = type_rank_vec
|
||||
|
|
@ -234,37 +311,45 @@ impl PlaceData {
|
|||
.enumerate()
|
||||
.filter_map(|(idx, &rank)| if rank == 0 { Some(idx) } else { None })
|
||||
.collect();
|
||||
let city_candidates: Vec<CityCandidate<'_>> = city_indices
|
||||
.iter()
|
||||
.map(|&idx| CityCandidate {
|
||||
name: &name[idx],
|
||||
lat: lat[idx],
|
||||
lon: lon[idx],
|
||||
})
|
||||
.collect();
|
||||
|
||||
let city: Vec<Option<String>> = (0..row_count)
|
||||
let fallback_city: Vec<Option<String>> = (0..row_count)
|
||||
.map(|idx| {
|
||||
if type_rank_vec[idx] == 0 {
|
||||
return None; // Cities don't need a city label
|
||||
}
|
||||
let plat = lat[idx];
|
||||
let plon = lon[idx];
|
||||
let cos_lat = (plat.to_radians()).cos();
|
||||
|
||||
let mut best_dist_sq = f32::MAX;
|
||||
let mut best_city: Option<&str> = None;
|
||||
for &ci in &city_indices {
|
||||
let dlat = lat[ci] - plat;
|
||||
let dlon = (lon[ci] - plon) * cos_lat;
|
||||
let dist_sq = dlat * dlat + dlon * dlon;
|
||||
if dist_sq < best_dist_sq {
|
||||
best_dist_sq = dist_sq;
|
||||
best_city = Some(&name[ci]);
|
||||
}
|
||||
}
|
||||
|
||||
// ~100km threshold: 1° ≈ 111km, so 0.9° ≈ 100km → 0.81 squared
|
||||
if best_dist_sq < 0.81 {
|
||||
best_city.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
nearest_display_city(lat[idx], lon[idx], &city_candidates).map(str::to_string)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let city: Vec<Option<String>> = if let Some(display_city_override) = display_city_override {
|
||||
fallback_city
|
||||
.into_iter()
|
||||
.zip(display_city_override)
|
||||
.enumerate()
|
||||
.map(|(idx, (fallback, override_city))| {
|
||||
if type_rank_vec[idx] == 0 {
|
||||
return None;
|
||||
}
|
||||
override_city
|
||||
.and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
})
|
||||
.or(fallback)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
fallback_city
|
||||
};
|
||||
|
||||
let with_pop = population.iter().filter(|&&pop| pop > 0).count();
|
||||
let with_city = city.iter().filter(|c| c.is_some()).count();
|
||||
info!(
|
||||
|
|
@ -294,6 +379,36 @@ impl PlaceData {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_city_candidates() -> Vec<CityCandidate<'static>> {
|
||||
vec![
|
||||
CityCandidate {
|
||||
name: "London",
|
||||
lat: 51.5074456,
|
||||
lon: -0.1277653,
|
||||
},
|
||||
CityCandidate {
|
||||
name: "Westminster",
|
||||
lat: 51.4973206,
|
||||
lon: -0.137149,
|
||||
},
|
||||
CityCandidate {
|
||||
name: "City of London",
|
||||
lat: 51.5156177,
|
||||
lon: -0.0919983,
|
||||
},
|
||||
CityCandidate {
|
||||
name: "Cambridge",
|
||||
lat: 52.2055314,
|
||||
lon: 0.1186637,
|
||||
},
|
||||
CityCandidate {
|
||||
name: "Oxford",
|
||||
lat: 51.7520131,
|
||||
lon: -1.2578499,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_rank_ordering() {
|
||||
assert!(type_rank("city") < type_rank("town"));
|
||||
|
|
@ -316,4 +431,41 @@ mod tests {
|
|||
assert!(!is_travel_destination_type("town"));
|
||||
assert!(!is_travel_destination_type("suburb"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nearest_display_city_canonicalizes_greater_london_aliases() {
|
||||
let cities = test_city_candidates();
|
||||
|
||||
assert_eq!(
|
||||
nearest_display_city(51.3713049, -0.101957, &cities),
|
||||
Some("London")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nearest_display_city_preserves_non_london_duplicates() {
|
||||
let cities = test_city_candidates();
|
||||
|
||||
assert_eq!(
|
||||
nearest_display_city(52.1277704, -0.0813098, &cities),
|
||||
Some("Cambridge")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nearest_display_city_does_not_leak_westminster_label_past_london_guard() {
|
||||
let cities = test_city_candidates();
|
||||
|
||||
assert_eq!(nearest_display_city(51.5093, -0.5954, &cities), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nearest_display_city_keeps_normal_non_london_city() {
|
||||
let cities = test_city_candidates();
|
||||
|
||||
assert_eq!(
|
||||
nearest_display_city(51.456659, -0.969651, &cities),
|
||||
Some("Oxford")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use super::places::{nearest_display_city, CityCandidate};
|
||||
use super::PlaceData;
|
||||
|
||||
/// Precomputed outcode data derived from postcode boundaries.
|
||||
|
|
@ -58,29 +59,18 @@ impl OutcodeData {
|
|||
.enumerate()
|
||||
.filter_map(|(idx, &rank)| if rank == 0 { Some(idx) } else { None })
|
||||
.collect();
|
||||
let city_candidates: Vec<CityCandidate<'_>> = city_indices
|
||||
.iter()
|
||||
.map(|&idx| CityCandidate {
|
||||
name: &place_data.name[idx],
|
||||
lat: place_data.lat[idx],
|
||||
lon: place_data.lon[idx],
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cities: Vec<Option<String>> = centroids
|
||||
.iter()
|
||||
.map(|&(lat, lon)| {
|
||||
let cos_lat = lat.to_radians().cos();
|
||||
let mut best_dist_sq = f32::MAX;
|
||||
let mut best_city: Option<&str> = None;
|
||||
for &ci in &city_indices {
|
||||
let dlat = place_data.lat[ci] - lat;
|
||||
let dlon = (place_data.lon[ci] - lon) * cos_lat;
|
||||
let dist_sq = dlat * dlat + dlon * dlon;
|
||||
if dist_sq < best_dist_sq {
|
||||
best_dist_sq = dist_sq;
|
||||
best_city = Some(&place_data.name[ci]);
|
||||
}
|
||||
}
|
||||
// ~100km threshold
|
||||
if best_dist_sq < 0.81 {
|
||||
best_city.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|&(lat, lon)| nearest_display_city(lat, lon, &city_candidates).map(str::to_string))
|
||||
.collect();
|
||||
|
||||
info!(
|
||||
|
|
|
|||
|
|
@ -160,6 +160,11 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|||
raw: false,
|
||||
absolute: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
FeatureGroup {
|
||||
name: "Defining characteristics",
|
||||
features: &[
|
||||
Feature::Numeric(FeatureConfig {
|
||||
name: "Street tree density percentile",
|
||||
bounds: Bounds::Fixed {
|
||||
|
|
@ -175,6 +180,21 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|||
raw: false,
|
||||
absolute: true,
|
||||
}),
|
||||
Feature::Numeric(FeatureConfig {
|
||||
name: "Noise (dB)",
|
||||
bounds: Bounds::Fixed {
|
||||
min: 50.0,
|
||||
max: 80.0,
|
||||
},
|
||||
step: 1.0,
|
||||
description: "Maximum transport noise level near the postcode in decibels (Lden)",
|
||||
detail: "Maximum road, rail, or airport noise level in decibels (Lden, a 24-hour weighted average) from Defra's Strategic Noise Mapping Round 4 (2022). Modelled at 4m above ground on a 10m grid and sampled as the maximum 10m cell around the postcode representative point. Above ~55 dB is typically noticeable; above ~70 dB is considered harmful by the WHO.",
|
||||
source: "noise",
|
||||
prefix: "",
|
||||
suffix: " dB",
|
||||
raw: false,
|
||||
absolute: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
FeatureGroup {
|
||||
|
|
@ -270,7 +290,7 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|||
],
|
||||
},
|
||||
FeatureGroup {
|
||||
name: "Education",
|
||||
name: "Schools",
|
||||
features: &[
|
||||
Feature::Numeric(FeatureConfig {
|
||||
name: "Good+ primary schools within 2km",
|
||||
|
|
@ -983,21 +1003,6 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|||
FeatureGroup {
|
||||
name: "Amenities",
|
||||
features: &[
|
||||
Feature::Numeric(FeatureConfig {
|
||||
name: "Noise (dB)",
|
||||
bounds: Bounds::Fixed {
|
||||
min: 50.0,
|
||||
max: 80.0,
|
||||
},
|
||||
step: 1.0,
|
||||
description: "Maximum transport noise level near the postcode in decibels (Lden)",
|
||||
detail: "Maximum road, rail, or airport noise level in decibels (Lden, a 24-hour weighted average) from Defra's Strategic Noise Mapping Round 4 (2022). Modelled at 4m above ground on a 10m grid and sampled as the maximum 10m cell around the postcode representative point. Above ~55 dB is typically noticeable; above ~70 dB is considered harmful by the WHO.",
|
||||
source: "noise",
|
||||
prefix: "",
|
||||
suffix: " dB",
|
||||
raw: false,
|
||||
absolute: false,
|
||||
}),
|
||||
Feature::Enum(EnumFeatureConfig {
|
||||
name: "Max available download speed (Mbps)",
|
||||
order: Some(&["10", "30", "100", "300", "1000"]),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod aggregation;
|
||||
mod auth;
|
||||
mod bugsink;
|
||||
mod checkout_sessions;
|
||||
mod consts;
|
||||
mod data;
|
||||
|
|
@ -29,6 +30,7 @@ use axum::Router;
|
|||
use clap::Parser;
|
||||
use consts::SERVICE_CALL_TIMEOUT;
|
||||
use tower::limit::ConcurrencyLimitLayer;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
use tower_http::cors::{AllowHeaders, AllowMethods, CorsLayer};
|
||||
|
|
@ -223,10 +225,87 @@ struct Cli {
|
|||
/// Google OAuth client secret for PocketBase SSO
|
||||
#[arg(long, env = "GOOGLE_OAUTH_CLIENT_SECRET")]
|
||||
google_oauth_client_secret: String,
|
||||
|
||||
/// Bugsink DSN for backend error reporting
|
||||
#[arg(long, env = "BUGSINK_DSN", hide_env_values = true)]
|
||||
bugsink_dsn: Option<String>,
|
||||
|
||||
/// Bugsink DSN injected into the browser app; falls back to BUGSINK_DSN when omitted
|
||||
#[arg(long, env = "FRONTEND_BUGSINK_DSN", hide_env_values = true)]
|
||||
frontend_bugsink_dsn: Option<String>,
|
||||
|
||||
/// Bugsink/Sentry environment name
|
||||
#[arg(long, env = "BUGSINK_ENVIRONMENT")]
|
||||
bugsink_environment: Option<String>,
|
||||
|
||||
/// Bugsink/Sentry release name
|
||||
#[arg(long, env = "BUGSINK_RELEASE")]
|
||||
bugsink_release: Option<String>,
|
||||
|
||||
/// Include default PII in Bugsink events
|
||||
#[arg(long, env = "BUGSINK_SEND_DEFAULT_PII", default_value_t = false)]
|
||||
bugsink_send_default_pii: bool,
|
||||
}
|
||||
|
||||
async fn capture_server_error_responses(
|
||||
request: axum::extract::Request,
|
||||
next: middleware::Next,
|
||||
) -> axum::response::Response {
|
||||
let method = request.method().clone();
|
||||
let path = request.uri().path().to_owned();
|
||||
let response = next.run(request).await;
|
||||
let status = response.status();
|
||||
|
||||
if status.is_server_error() {
|
||||
sentry::with_scope(
|
||||
|scope| {
|
||||
scope.set_tag("http.status_code", status.as_u16().to_string());
|
||||
scope.set_tag("http.method", method.to_string());
|
||||
scope.set_tag("http.route", path.clone());
|
||||
},
|
||||
|| {
|
||||
sentry::capture_message(
|
||||
&format!("HTTP {status} response from {method} {path}"),
|
||||
sentry::Level::Error,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let bugsink_environment = cli
|
||||
.bugsink_environment
|
||||
.clone()
|
||||
.or_else(|| bugsink::env_nonempty("SENTRY_ENVIRONMENT"));
|
||||
let bugsink_release = cli
|
||||
.bugsink_release
|
||||
.clone()
|
||||
.or_else(|| bugsink::env_nonempty("SENTRY_RELEASE"));
|
||||
let backend_bugsink_dsn = cli
|
||||
.bugsink_dsn
|
||||
.clone()
|
||||
.or_else(|| bugsink::env_nonempty("SENTRY_DSN"));
|
||||
let _bugsink_guard = bugsink::init_backend(&bugsink::BackendConfig {
|
||||
dsn: backend_bugsink_dsn.clone(),
|
||||
environment: bugsink_environment.clone(),
|
||||
release: bugsink_release.clone(),
|
||||
send_default_pii: cli.bugsink_send_default_pii,
|
||||
});
|
||||
let bugsink_frontend_config = bugsink::frontend_config(
|
||||
cli.frontend_bugsink_dsn
|
||||
.clone()
|
||||
.or_else(|| bugsink::env_nonempty("PUBLIC_BUGSINK_DSN"))
|
||||
.or(backend_bugsink_dsn),
|
||||
bugsink_environment.clone(),
|
||||
bugsink_release.clone(),
|
||||
cli.bugsink_send_default_pii,
|
||||
);
|
||||
|
||||
let file_appender = tracing_appender::rolling::daily("logs", "server.log");
|
||||
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
|
|
@ -234,6 +313,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(sentry::integrations::tracing::layer())
|
||||
.with(tracing_subscriber::fmt::layer().with_ansi(true))
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
|
|
@ -245,8 +325,9 @@ async fn main() -> anyhow::Result<()> {
|
|||
// Initialize Prometheus metrics
|
||||
let metrics_handle = metrics::init_metrics();
|
||||
info!("Prometheus metrics initialized");
|
||||
|
||||
let cli = Cli::parse();
|
||||
if bugsink_frontend_config.is_some() || _bugsink_guard.is_some() {
|
||||
info!("Bugsink error reporting configured");
|
||||
}
|
||||
|
||||
for (label, path) in [
|
||||
("Properties", &cli.properties),
|
||||
|
|
@ -483,6 +564,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
stripe_secret_key: cli.stripe_secret_key,
|
||||
stripe_webhook_secret: cli.stripe_webhook_secret,
|
||||
stripe_referral_coupon_id: cli.stripe_referral_coupon_id,
|
||||
bugsink_frontend_config,
|
||||
};
|
||||
|
||||
let shared = Arc::new(SharedState::new(app_state));
|
||||
|
|
@ -670,9 +752,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
.route("/health", get(|| async { "ok" }))
|
||||
.route(
|
||||
"/metrics",
|
||||
get(move |connect_info| {
|
||||
metrics::metrics_handler(metrics_handle.clone(), connect_info)
|
||||
}),
|
||||
get(move |connect_info| metrics::metrics_handler(metrics_handle.clone(), connect_info)),
|
||||
)
|
||||
.with_state(shared.clone());
|
||||
|
||||
|
|
@ -696,9 +776,17 @@ async fn main() -> anyhow::Result<()> {
|
|||
},
|
||||
))
|
||||
.layer(middleware::from_fn(static_cache_headers))
|
||||
.layer(middleware::from_fn(capture_server_error_responses))
|
||||
.layer(cors)
|
||||
.layer(CompressionLayer::new().zstd(true).gzip(true))
|
||||
.layer(TraceLayer::new_for_http());
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(sentry::integrations::tower::NewSentryLayer::<
|
||||
axum::extract::Request,
|
||||
>::new_from_top())
|
||||
.layer(sentry::integrations::tower::SentryHttpLayer::new()),
|
||||
);
|
||||
|
||||
// Lock all current and future memory pages to prevent swapping
|
||||
unsafe {
|
||||
|
|
|
|||
|
|
@ -174,9 +174,9 @@ fn is_same_network(ip: IpAddr) -> bool {
|
|||
v6.is_loopback()
|
||||
|| (v6.segments()[0] & 0xfe00) == 0xfc00
|
||||
|| (v6.segments()[0] & 0xffc0) == 0xfe80
|
||||
|| v6.to_ipv4_mapped().is_some_and(|v4| {
|
||||
v4.is_loopback() || v4.is_private() || v4.is_link_local()
|
||||
})
|
||||
|| v6
|
||||
.to_ipv4_mapped()
|
||||
.is_some_and(|v4| v4.is_loopback() || v4.is_private() || v4.is_link_local())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use crate::state::AppState;
|
|||
|
||||
const OG_PLACEHOLDER: &str =
|
||||
r#"<meta name="x-og-placeholder" content="__PERFECT_POSTCODE_OG_TAGS__"/>"#;
|
||||
const BUGSINK_CONFIG_PLACEHOLDER: &str = "__PERFECT_POSTCODE_BUGSINK_CONFIG__";
|
||||
|
||||
const HTML_BODY_LIMIT: usize = 5 * 1024 * 1024;
|
||||
|
||||
|
|
@ -318,6 +319,26 @@ fn inject_tags(mut html: String, page: &SeoPage, tags: &str) -> String {
|
|||
html
|
||||
}
|
||||
|
||||
fn escape_json_for_script_tag(json: &str) -> String {
|
||||
json.replace('&', "\\u0026")
|
||||
.replace('<', "\\u003c")
|
||||
.replace('>', "\\u003e")
|
||||
}
|
||||
|
||||
fn inject_bugsink_config(html: String, config: Option<&crate::bugsink::FrontendConfig>) -> String {
|
||||
if !html.contains(BUGSINK_CONFIG_PLACEHOLDER) {
|
||||
return html;
|
||||
}
|
||||
|
||||
let json = config
|
||||
.and_then(|config| serde_json::to_string(config).ok())
|
||||
.unwrap_or_else(|| "{}".to_string());
|
||||
html.replace(
|
||||
BUGSINK_CONFIG_PLACEHOLDER,
|
||||
&escape_json_for_script_tag(&json),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn og_middleware(request: Request, next: Next) -> Response {
|
||||
let path = request.uri().path().to_string();
|
||||
// Capture the query string before passing the request through
|
||||
|
|
@ -360,10 +381,10 @@ pub async fn og_middleware(request: Request, next: Next) -> Response {
|
|||
None => return response,
|
||||
};
|
||||
|
||||
let page = match seo_page_for_path(&path) {
|
||||
Some(page) => page,
|
||||
None => return response,
|
||||
};
|
||||
let page = seo_page_for_path(&path);
|
||||
if page.is_none() && state.bugsink_frontend_config.is_none() {
|
||||
return response;
|
||||
}
|
||||
|
||||
let (mut parts, body) = response.into_parts();
|
||||
let bytes = match to_bytes(body, HTML_BODY_LIMIT).await {
|
||||
|
|
@ -377,8 +398,11 @@ pub async fn og_middleware(request: Request, next: Next) -> Response {
|
|||
};
|
||||
|
||||
let html = String::from_utf8_lossy(&bytes).into_owned();
|
||||
let tags = route_seo_tags(&page, &path, &query_string, &state.public_url, language);
|
||||
let html = inject_tags(html, &page, &tags);
|
||||
let mut html = inject_bugsink_config(html, state.bugsink_frontend_config.as_ref());
|
||||
if let Some(page) = page {
|
||||
let tags = route_seo_tags(&page, &path, &query_string, &state.public_url, language);
|
||||
html = inject_tags(html, &page, &tags);
|
||||
}
|
||||
parts.headers.remove(header::CONTENT_LENGTH);
|
||||
Response::from_parts(parts, Body::from(html))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,13 @@ use crate::data::{Histogram, PropertyData};
|
|||
use crate::features::{self, Feature, FEATURE_GROUPS};
|
||||
use crate::state::SharedState;
|
||||
|
||||
const FILTER_GROUP_ORDER: &[&str] = &["Transport", "Property prices", "Properties", "Amenities"];
|
||||
const FILTER_GROUP_ORDER: &[&str] = &[
|
||||
"Transport",
|
||||
"Property prices",
|
||||
"Properties",
|
||||
"Defining characteristics",
|
||||
"Amenities",
|
||||
];
|
||||
const LAST_FILTER_GROUPS: &[&str] = &["Area development"];
|
||||
const POI_DISTANCE_SLIDER_MIN_KM: f32 = 0.0;
|
||||
const POI_DISTANCE_SLIDER_MAX_KM: f32 = 5.0;
|
||||
|
|
@ -268,11 +274,12 @@ mod tests {
|
|||
fn orders_filter_groups_for_backend_response() {
|
||||
let mut groups = vec![
|
||||
group("Properties"),
|
||||
group("Education"),
|
||||
group("Schools"),
|
||||
group("Area development"),
|
||||
group("Property prices"),
|
||||
group("Crime"),
|
||||
group("Neighbours"),
|
||||
group("Defining characteristics"),
|
||||
group("Amenities"),
|
||||
group("Transport"),
|
||||
];
|
||||
|
|
@ -286,8 +293,9 @@ mod tests {
|
|||
"Transport",
|
||||
"Property prices",
|
||||
"Properties",
|
||||
"Defining characteristics",
|
||||
"Amenities",
|
||||
"Education",
|
||||
"Schools",
|
||||
"Crime",
|
||||
"Neighbours",
|
||||
"Area development",
|
||||
|
|
|
|||
|
|
@ -633,7 +633,8 @@ mod tests {
|
|||
|
||||
assert!(fields_specified);
|
||||
assert!(field_set.contains("Property type"));
|
||||
assert!(!field_set.contains("Noise (dB)"));
|
||||
assert!(field_set.contains("Street tree density percentile"));
|
||||
assert!(field_set.contains("Noise (dB)"));
|
||||
assert!(!field_set.contains("Max available download speed (Mbps)"));
|
||||
assert!(!field_set.contains("Distance to nearest amenity (Cafe) (km)"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::Extension;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::Json;
|
||||
use axum::Extension;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::auth::OptionalUser;
|
||||
use crate::data::{PlaceData, slugify};
|
||||
use crate::data::{slugify, PlaceData};
|
||||
use crate::licensing::{check_license_point, resolve_share_code};
|
||||
use crate::state::SharedState;
|
||||
use crate::utils::normalize_postcode;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use parking_lot::RwLock;
|
|||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::auth::TokenCache;
|
||||
use crate::bugsink::FrontendConfig as BugsinkFrontendConfig;
|
||||
use crate::data::{
|
||||
OutcodeData, POICategoryGroup, POIData, PlaceData, PostcodeData, PropertyData, TravelTimeStore,
|
||||
};
|
||||
|
|
@ -77,6 +78,8 @@ pub struct AppState {
|
|||
pub stripe_webhook_secret: String,
|
||||
/// Stripe Coupon ID for referral discounts
|
||||
pub stripe_referral_coupon_id: String,
|
||||
/// Bugsink/Sentry-compatible browser error reporting config injected into served HTML.
|
||||
pub bugsink_frontend_config: Option<BugsinkFrontendConfig>,
|
||||
}
|
||||
|
||||
/// Wraps AppState for shared access across route handlers.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue