correctly handle updated alerts

This commit is contained in:
Sebastian Hugentobler 2024-05-11 18:37:41 +02:00
parent a8c0be2c05
commit ac0ac8a10d
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
34 changed files with 775 additions and 114 deletions

520
Cargo.lock generated
View File

@ -56,11 +56,15 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"config", "config",
"entity",
"futures",
"migration",
"reqwest", "reqwest",
"rumqttc", "rumqttc",
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
"thiserror",
"time", "time",
"tokio", "tokio",
"tracing", "tracing",
@ -148,6 +152,155 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-channel"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928"
dependencies = [
"concurrent-queue",
"event-listener 5.3.0",
"event-listener-strategy 0.5.2",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand 2.1.0",
"futures-lite 2.3.0",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
"async-channel 2.2.1",
"async-executor",
"async-io 2.3.2",
"async-lock 3.3.0",
"blocking",
"futures-lite 2.3.0",
"once_cell",
"tokio",
]
[[package]]
name = "async-io"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock 2.8.0",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-lite 1.13.0",
"log",
"parking",
"polling 2.8.0",
"rustix 0.37.27",
"slab",
"socket2 0.4.10",
"waker-fn",
]
[[package]]
name = "async-io"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
dependencies = [
"async-lock 3.3.0",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.3.0",
"parking",
"polling 3.7.0",
"rustix 0.38.34",
"slab",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
dependencies = [
"event-listener 4.0.3",
"event-listener-strategy 0.4.0",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel 1.9.0",
"async-global-executor",
"async-io 1.13.0",
"async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.5" version = "0.3.5"
@ -170,6 +323,12 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.80" version = "0.1.80"
@ -190,6 +349,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.2.0" version = "1.2.0"
@ -276,6 +441,20 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "blocking"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88"
dependencies = [
"async-channel 2.2.1",
"async-lock 3.3.0",
"async-task",
"futures-io",
"futures-lite 2.3.0",
"piper",
]
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.4.0" version = "1.4.0"
@ -417,6 +596,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "config" name = "config"
version = "0.14.0" version = "0.14.0"
@ -611,6 +799,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "entity"
version = "0.1.0"
dependencies = [
"sea-orm",
"serde",
"time",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -644,6 +841,57 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
dependencies = [
"event-listener 4.0.3",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener 5.3.0",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.1.0" version = "2.1.0"
@ -696,6 +944,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -746,6 +995,45 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
dependencies = [
"fastrand 2.1.0",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.30" version = "0.3.30"
@ -767,6 +1055,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
@ -802,6 +1091,24 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo-timers"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -979,7 +1286,7 @@ dependencies = [
"http-body", "http-body",
"hyper", "hyper",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.7",
"tokio", "tokio",
"tower", "tower",
"tower-service", "tower-service",
@ -1040,6 +1347,26 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@ -1081,6 +1408,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1119,6 +1455,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.13"
@ -1140,6 +1482,9 @@ name = "log"
version = "0.4.21" version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
@ -1166,6 +1511,14 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "migration"
version = "0.1.0"
dependencies = [
"async-std",
"sea-orm-migration",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@ -1362,6 +1715,12 @@ 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 = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.2" version = "0.12.2"
@ -1489,6 +1848,17 @@ 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 = "piper"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
dependencies = [
"atomic-waker",
"fastrand 2.1.0",
"futures-io",
]
[[package]] [[package]]
name = "pkcs1" name = "pkcs1"
version = "0.7.5" version = "0.7.5"
@ -1516,6 +1886,37 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]]
name = "polling"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 0.38.34",
"tracing",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@ -1873,6 +2274,20 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.34" version = "0.38.34"
@ -1882,7 +2297,7 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys 0.4.13",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -2042,6 +2457,23 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "sea-orm-cli"
version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "620bc560062ae251b1366bde43b3f1508445cab5c2c8cbdb397034638ab1b357"
dependencies = [
"chrono",
"clap",
"dotenvy",
"glob",
"regex",
"sea-schema",
"tracing",
"tracing-subscriber",
"url",
]
[[package]] [[package]]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "0.12.15" version = "0.12.15"
@ -2056,6 +2488,23 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sea-orm-migration"
version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8269bc6ff71afd6b78aa4333ac237a69eebd2cdb439036291e64fb4b8db23c"
dependencies = [
"async-trait",
"clap",
"dotenvy",
"futures",
"sea-orm",
"sea-orm-cli",
"sea-schema",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "sea-query" name = "sea-query"
version = "0.30.7" version = "0.30.7"
@ -2068,6 +2517,7 @@ dependencies = [
"inherent", "inherent",
"ordered-float", "ordered-float",
"rust_decimal", "rust_decimal",
"sea-query-derive",
"serde_json", "serde_json",
"time", "time",
"uuid", "uuid",
@ -2089,6 +2539,42 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "sea-query-derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.60",
"thiserror",
]
[[package]]
name = "sea-schema"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d148608012d25222442d1ebbfafd1228dbc5221baf4ec35596494e27a2394e"
dependencies = [
"futures",
"sea-query",
"sea-schema-derive",
]
[[package]]
name = "sea-schema-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f686050f76bffc4f635cda8aea6df5548666b830b52387e8bc7de11056d11e"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "seahash" name = "seahash"
version = "4.1.0" version = "4.1.0"
@ -2241,6 +2727,16 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.7" version = "0.5.7"
@ -2315,7 +2811,7 @@ dependencies = [
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
"event-listener", "event-listener 2.5.3",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-intrusive", "futures-intrusive",
@ -2591,8 +3087,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand 2.1.0",
"rustix", "rustix 0.38.34",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -2695,7 +3191,7 @@ dependencies = [
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2 0.5.7",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2963,6 +3459,12 @@ 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 = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -2975,6 +3477,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"

View File

@ -1,17 +1,15 @@
[package] [workspace]
name = "alert-me" resolver = "2"
version = "0.1.0" members = [
edition = "2021" "app", "migration",
]
[dependencies] [workspace.dependencies]
clap = { version = "4.5.4", features = ["derive"] } sea-orm = "0.12"
config = "0.14.0"
reqwest = { version = "0.12.4", features = ["json", "rustls-tls"], default-features = false }
rumqttc = "0.24.0"
sea-orm = { version = "0.12", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ] }
serde = "1.0.199" serde = "1.0.199"
serde_json = "1.0.116" time = "0.3.36"
time = { version = "0.3.36", features = ["macros", "serde", "formatting", "parsing" ] }
tokio = { version = "1.37.0", features = ["full"] } [workspace.package]
tracing = "0.1.40" license = "AGPL-3.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } authors = ["Sebastian Hugentobler <shu@vanwa.ch>"]
repository = "https://code.vanwa.ch/shu/alert-me"

21
app/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "alert-me"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { path = "../entity" }
migration = { path = "../migration" }
clap = { version = "4.5.4", features = ["derive"] }
config = "0.14.0"
futures = "0.3.30"
reqwest = { version = "0.12.4", features = ["json", "rustls-tls"], default-features = false }
rumqttc = "0.24.0"
sea-orm = { workspace = true, features = [ "with-time", "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls", "macros" ] }
serde = { workspace = true }
serde_json = "1.0.116"
thiserror = "1.0.59"
time = { workspace = true, features = ["macros", "serde", "formatting", "parsing" ] }
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View File

@ -9,4 +9,7 @@ pub struct Cli {
/// Update interval in seconds /// Update interval in seconds
#[arg(short, long, default_value_t = 10)] #[arg(short, long, default_value_t = 10)]
pub interval: u64, pub interval: u64,
/// Database connection string where timestamps are stored (SQLite, Postgres or MySql)
#[arg(short, long, default_value_t = format!("sqlite::memory:"))]
pub connection: String,
} }

View File

@ -4,12 +4,15 @@ use crate::cli::Cli;
pub struct Config { pub struct Config {
/// Update interval in seconds /// Update interval in seconds
pub interval: u64, pub interval: u64,
/// Database connection string where timestamps are stored (SQLite, Postgres or MySql)
pub connection: String,
} }
impl From<Cli> for Config { impl From<Cli> for Config {
fn from(value: Cli) -> Self { fn from(value: Cli) -> Self {
Self { Self {
interval: value.interval, interval: value.interval,
connection: value.connection,
} }
} }
} }

View File

@ -0,0 +1,50 @@
use entity::alerts;
use migration::{Migrator, MigratorTrait};
use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, DbErr, EntityTrait, Set};
use tracing::debug;
use crate::json::alert::Alert;
pub struct Db {
connection: DatabaseConnection,
}
impl Db {
pub async fn is_alert_updated(&self, alert: &Alert) -> Result<bool, DbErr> {
let db_alert = entity::alerts::Entity::find_by_id(&alert.identifier)
.one(&self.connection)
.await?;
Ok(match db_alert {
Some(db_alert) => db_alert.publish_date < alert.publish_date,
None => true,
})
}
pub async fn save_alert(&self, alert: &Alert) -> Result<(), DbErr> {
let db_alert = entity::alerts::Entity::find_by_id(&alert.identifier)
.one(&self.connection)
.await?;
let alert = alerts::ActiveModel {
id: Set(alert.identifier.to_owned()),
publish_date: Set(alert.publish_date),
};
match db_alert {
Some(_) => alert.save(&self.connection).await?,
None => alert.insert(&self.connection).await?.into(),
};
Ok(())
}
}
pub async fn connect(connection_string: &str) -> Result<Db, DbErr> {
debug!("connecting to {connection_string}...");
let connection: DatabaseConnection = Database::connect(connection_string).await?;
Migrator::up(&connection, None).await?;
Ok(Db { connection })
}

13
app/src/error.rs Normal file
View File

@ -0,0 +1,13 @@
use sea_orm::DbErr;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("opds error")]
pub enum Error {
/// Error rendering OPDS.
#[error("opds error")]
DbError(#[from] DbErr),
/// Error fetching data from calibre.
#[error("data error")]
FetchError(#[from] reqwest::Error),
}

View File

@ -7,7 +7,7 @@ use super::{
title::Title, title::Title,
}; };
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Alert { pub struct Alert {
pub identifier: String, pub identifier: String,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::{circle::Circle, description::Description, polygon::Polygon, region::Region}; use super::{circle::Circle, description::Description, polygon::Polygon, region::Region};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Area { pub struct Area {
pub description: Description, pub description: Description,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::coordinate::Coordinate; use super::coordinate::Coordinate;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Circle { pub struct Circle {
pub center_position: Coordinate, pub center_position: Coordinate,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Contact { pub struct Contact {
pub contact: String, pub contact: String,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Coordinate { pub struct Coordinate {
#[serde(rename = "0")] #[serde(rename = "0")]

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Description { pub struct Description {
pub description: String, pub description: String,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Image { pub struct Image {
pub uri: String, pub uri: String,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::description::Description; use super::description::Description;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ImageDescription { pub struct ImageDescription {
pub index: u64, pub index: u64,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::title::Title; use super::title::Title;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ImageTitle { pub struct ImageTitle {
pub index: u64, pub index: u64,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Link { pub struct Link {
pub href: String, pub href: String,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::coordinate::Coordinate; use super::coordinate::Coordinate;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Polygon { pub struct Polygon {
pub coordinates: Vec<Coordinate>, pub coordinates: Vec<Coordinate>,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Region { pub struct Region {
pub region: String, pub region: String,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Text { pub struct Text {
pub text: String, pub text: String,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Title { pub struct Title {
pub title: String, pub title: String,

81
app/src/lib.rs Normal file
View File

@ -0,0 +1,81 @@
use std::time::Duration;
use config::Config;
use datastore::rdbms::Db;
use error::Error;
use futures::{stream, StreamExt};
use tokio::time;
use tracing::{error, info};
use crate::json::alert::Alert;
pub mod alert_swiss;
pub mod cli;
pub mod config;
pub mod error;
pub mod datastore {
pub mod rdbms;
}
mod json {
pub mod alert;
pub mod alerts;
pub mod area;
pub mod circle;
pub mod contact;
pub mod coordinate;
pub mod datetime;
pub mod description;
pub mod image;
pub mod image_description;
pub mod image_title;
pub mod link;
pub mod polygon;
pub mod region;
pub mod text;
pub mod title;
}
pub mod logging;
async fn handle_alerts(db: &Db) -> Result<Vec<Alert>, Error> {
info!("checking alerts");
let alerts = alert_swiss::fetch_alerts().await?;
let alerts: Vec<Alert> = alerts
.list
.into_iter()
.filter(|alert| !alert.identifier.starts_with("TEST-"))
.collect();
let new_alerts: Vec<Alert> = stream::iter(alerts)
.filter_map(|alert| async move {
if db.is_alert_updated(&alert).await.ok().unwrap_or(false) {
Some(alert)
} else {
None
}
})
.collect()
.await;
stream::iter(new_alerts.clone())
.for_each_concurrent(None, |alert| async move {
info!("saving alert {}", alert.identifier);
match db.save_alert(&alert).await {
Ok(_) => {}
Err(e) => error!("failed to save alert: {e}"),
};
})
.await;
Ok(new_alerts)
}
pub async fn run(config: Config) -> Result<(), Error> {
let mut interval = time::interval(Duration::from_secs(config.interval));
let db = &datastore::rdbms::connect(&config.connection).await?;
loop {
interval.tick().await;
let new_alerts = handle_alerts(db).await?;
}
}

View File

@ -2,7 +2,7 @@ use tracing::debug;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
pub fn setup(bin_name: &str) { pub fn setup(bin_name: &str) {
let default_config = format!("{}=info,tower_http=info", bin_name); let default_config = format!("{}=info,tower_http=info,sqlx=error", bin_name);
tracing_subscriber::registry() tracing_subscriber::registry()
.with( .with(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()

18
app/src/main.rs Normal file
View File

@ -0,0 +1,18 @@
use std::process;
use alert_me::{cli::Cli, logging};
use clap::Parser;
use tracing::error;
#[tokio::main]
async fn main() {
logging::setup("alert_me");
let args = Cli::parse();
let config = args.into();
if let Err(e) = alert_me::run(config).await {
error!("Application error: {e}");
process::exit(1);
}
}

12
entity/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
sea-orm = { workspace = true }
serde = { workspace = true }
time = { workspace = true }

18
entity/src/alerts.rs Normal file
View File

@ -0,0 +1,18 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "alerts")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub publish_date: PrimitiveDateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

5
entity/src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub mod prelude;
pub mod alerts;

3
entity/src/prelude.rs Normal file
View File

@ -0,0 +1,3 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub use super::alerts::Entity as Alerts;

View File

@ -6,42 +6,28 @@ pub struct Migration;
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
todo!();
manager manager
.create_table( .create_table(
Table::create() Table::create()
.table(Post::Table) .table(Alerts::Table)
.if_not_exists() .if_not_exists()
.col( .col(ColumnDef::new(Alerts::Id).string().not_null().primary_key())
ColumnDef::new(Post::Id) .col(ColumnDef::new(Alerts::PublishDate).date_time().not_null())
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Post::Title).string().not_null())
.col(ColumnDef::new(Post::Text).string().not_null())
.to_owned(), .to_owned(),
) )
.await .await
} }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
todo!();
manager manager
.drop_table(Table::drop().table(Post::Table).to_owned()) .drop_table(Table::drop().table(Alerts::Table).to_owned())
.await .await
} }
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
enum Post { enum Alerts {
Table, Table,
Id, Id,
Title, PublishDate,
Text,
} }

View File

@ -1,42 +0,0 @@
use std::time::Duration;
use config::Config;
use tokio::time;
use tracing::debug;
use crate::json::alert::Alert;
pub mod alert_swiss;
pub mod cli;
pub mod config;
mod json {
pub mod alert;
pub mod alerts;
pub mod area;
pub mod circle;
pub mod contact;
pub mod coordinate;
pub mod datetime;
pub mod description;
pub mod image;
pub mod image_description;
pub mod image_title;
pub mod link;
pub mod polygon;
pub mod region;
pub mod text;
pub mod title;
}
pub mod logging;
pub async fn run(config: Config) {
let mut interval = time::interval(Duration::from_secs(config.interval));
loop {
interval.tick().await;
let alerts = alert_swiss::fetch_alerts().await.unwrap();
let new_alerts: Vec<Alert> = alerts.list.into_iter().filter(|x| true).collect();
debug!("{new_alerts:#?}");
}
}

View File

@ -1,16 +0,0 @@
use alert_me::{cli::Cli, logging};
use clap::Parser;
#[tokio::main]
async fn main() {
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "info");
}
logging::setup("alert-me");
let args = Cli::parse();
let config = args.into();
alert_me::run(config).await
}