From ac0ac8a10d862702322cc8dee76c9a9bf4921fee Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 11 May 2024 18:37:41 +0200 Subject: [PATCH] correctly handle updated alerts --- Cargo.lock | 520 +++++++++++++++++- Cargo.toml | 28 +- app/Cargo.toml | 21 + {src => app/src}/alert_swiss.rs | 0 {src => app/src}/cli.rs | 3 + {src => app/src}/config.rs | 3 + app/src/datastore/rdbms.rs | 50 ++ app/src/error.rs | 13 + {src => app/src}/json/alert.rs | 2 +- {src => app/src}/json/alerts.rs | 0 {src => app/src}/json/area.rs | 2 +- {src => app/src}/json/circle.rs | 2 +- {src => app/src}/json/contact.rs | 2 +- {src => app/src}/json/coordinate.rs | 2 +- {src => app/src}/json/datetime.rs | 0 {src => app/src}/json/description.rs | 2 +- {src => app/src}/json/image.rs | 2 +- {src => app/src}/json/image_description.rs | 2 +- {src => app/src}/json/image_title.rs | 2 +- {src => app/src}/json/link.rs | 2 +- {src => app/src}/json/polygon.rs | 2 +- {src => app/src}/json/region.rs | 2 +- {src => app/src}/json/text.rs | 2 +- {src => app/src}/json/title.rs | 2 +- app/src/lib.rs | 81 +++ {src => app/src}/logging.rs | 2 +- app/src/main.rs | 18 + entity/Cargo.toml | 12 + entity/src/alerts.rs | 18 + entity/src/lib.rs | 5 + entity/src/prelude.rs | 3 + .../src/m20220101_000001_create_table.rs | 26 +- src/lib.rs | 42 -- src/main.rs | 16 - 34 files changed, 775 insertions(+), 114 deletions(-) create mode 100644 app/Cargo.toml rename {src => app/src}/alert_swiss.rs (100%) rename {src => app/src}/cli.rs (58%) rename {src => app/src}/config.rs (62%) create mode 100644 app/src/datastore/rdbms.rs create mode 100644 app/src/error.rs rename {src => app/src}/json/alert.rs (96%) rename {src => app/src}/json/alerts.rs (100%) rename {src => app/src}/json/area.rs (87%) rename {src => app/src}/json/circle.rs (80%) rename {src => app/src}/json/contact.rs (77%) rename {src => app/src}/json/coordinate.rs (80%) rename {src => app/src}/json/datetime.rs (100%) rename {src => app/src}/json/description.rs (78%) rename {src => app/src}/json/image.rs (79%) rename {src => app/src}/json/image_description.rs (82%) rename {src => app/src}/json/image_title.rs (81%) rename {src => app/src}/json/link.rs (74%) rename {src => app/src}/json/polygon.rs (77%) rename {src => app/src}/json/region.rs (71%) rename {src => app/src}/json/text.rs (76%) rename {src => app/src}/json/title.rs (77%) create mode 100644 app/src/lib.rs rename {src => app/src}/logging.rs (84%) create mode 100644 app/src/main.rs create mode 100644 entity/Cargo.toml create mode 100644 entity/src/alerts.rs create mode 100644 entity/src/lib.rs create mode 100644 entity/src/prelude.rs delete mode 100644 src/lib.rs delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6b043cb..8c42751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,11 +56,15 @@ version = "0.1.0" dependencies = [ "clap", "config", + "entity", + "futures", + "migration", "reqwest", "rumqttc", "sea-orm", "serde", "serde_json", + "thiserror", "time", "tokio", "tracing", @@ -148,6 +152,155 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "async-stream" version = "0.3.5" @@ -170,6 +323,12 @@ dependencies = [ "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]] name = "async-trait" version = "0.1.80" @@ -190,6 +349,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.2.0" @@ -276,6 +441,20 @@ dependencies = [ "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]] name = "borsh" version = "1.4.0" @@ -417,6 +596,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "config" version = "0.14.0" @@ -611,6 +799,15 @@ dependencies = [ "serde", ] +[[package]] +name = "entity" +version = "0.1.0" +dependencies = [ + "sea-orm", + "serde", + "time", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -644,6 +841,57 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "fastrand" version = "2.1.0" @@ -696,6 +944,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -746,6 +995,45 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "futures-sink" version = "0.3.30" @@ -767,6 +1055,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -802,6 +1091,24 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "hashbrown" version = "0.12.3" @@ -979,7 +1286,7 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -1040,6 +1347,26 @@ dependencies = [ "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]] name = "ipnet" version = "2.9.0" @@ -1081,6 +1408,15 @@ dependencies = [ "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]] name = "lazy_static" version = "1.4.0" @@ -1119,6 +1455,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1140,6 +1482,9 @@ name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] [[package]] name = "matchers" @@ -1166,6 +1511,14 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "migration" +version = "0.1.0" +dependencies = [ + "async-std", + "sea-orm-migration", +] + [[package]] name = "mime" version = "0.3.17" @@ -1362,6 +1715,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.2" @@ -1489,6 +1848,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "pkcs1" version = "0.7.5" @@ -1516,6 +1886,37 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "powerfmt" version = "0.2.0" @@ -1873,6 +2274,20 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "rustix" version = "0.38.34" @@ -1882,7 +2297,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -2042,6 +2457,23 @@ dependencies = [ "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]] name = "sea-orm-macros" version = "0.12.15" @@ -2056,6 +2488,23 @@ dependencies = [ "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]] name = "sea-query" version = "0.30.7" @@ -2068,6 +2517,7 @@ dependencies = [ "inherent", "ordered-float", "rust_decimal", + "sea-query-derive", "serde_json", "time", "uuid", @@ -2089,6 +2539,42 @@ dependencies = [ "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]] name = "seahash" version = "4.1.0" @@ -2241,6 +2727,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "socket2" version = "0.5.7" @@ -2315,7 +2811,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -2591,8 +3087,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand", - "rustix", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -2695,7 +3191,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -2963,6 +3459,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2975,6 +3477,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index c76230a..070c203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,15 @@ -[package] -name = "alert-me" -version = "0.1.0" -edition = "2021" +[workspace] +resolver = "2" +members = [ + "app", "migration", +] -[dependencies] -clap = { version = "4.5.4", features = ["derive"] } -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" ] } +[workspace.dependencies] +sea-orm = "0.12" serde = "1.0.199" -serde_json = "1.0.116" -time = { version = "0.3.36", 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"] } +time = "0.3.36" + +[workspace.package] +license = "AGPL-3.0" +authors = ["Sebastian Hugentobler "] +repository = "https://code.vanwa.ch/shu/alert-me" diff --git a/app/Cargo.toml b/app/Cargo.toml new file mode 100644 index 0000000..4c06d83 --- /dev/null +++ b/app/Cargo.toml @@ -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"] } diff --git a/src/alert_swiss.rs b/app/src/alert_swiss.rs similarity index 100% rename from src/alert_swiss.rs rename to app/src/alert_swiss.rs diff --git a/src/cli.rs b/app/src/cli.rs similarity index 58% rename from src/cli.rs rename to app/src/cli.rs index 7e18eec..745698b 100644 --- a/src/cli.rs +++ b/app/src/cli.rs @@ -9,4 +9,7 @@ pub struct Cli { /// Update interval in seconds #[arg(short, long, default_value_t = 10)] 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, } diff --git a/src/config.rs b/app/src/config.rs similarity index 62% rename from src/config.rs rename to app/src/config.rs index d10543c..08d79cf 100644 --- a/src/config.rs +++ b/app/src/config.rs @@ -4,12 +4,15 @@ use crate::cli::Cli; pub struct Config { /// Update interval in seconds pub interval: u64, + /// Database connection string where timestamps are stored (SQLite, Postgres or MySql) + pub connection: String, } impl From for Config { fn from(value: Cli) -> Self { Self { interval: value.interval, + connection: value.connection, } } } diff --git a/app/src/datastore/rdbms.rs b/app/src/datastore/rdbms.rs new file mode 100644 index 0000000..9745ac7 --- /dev/null +++ b/app/src/datastore/rdbms.rs @@ -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 { + 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 { + debug!("connecting to {connection_string}..."); + + let connection: DatabaseConnection = Database::connect(connection_string).await?; + Migrator::up(&connection, None).await?; + + Ok(Db { connection }) +} diff --git a/app/src/error.rs b/app/src/error.rs new file mode 100644 index 0000000..6b8f8a4 --- /dev/null +++ b/app/src/error.rs @@ -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), +} diff --git a/src/json/alert.rs b/app/src/json/alert.rs similarity index 96% rename from src/json/alert.rs rename to app/src/json/alert.rs index 26e802a..2633536 100644 --- a/src/json/alert.rs +++ b/app/src/json/alert.rs @@ -7,7 +7,7 @@ use super::{ title::Title, }; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Alert { pub identifier: String, diff --git a/src/json/alerts.rs b/app/src/json/alerts.rs similarity index 100% rename from src/json/alerts.rs rename to app/src/json/alerts.rs diff --git a/src/json/area.rs b/app/src/json/area.rs similarity index 87% rename from src/json/area.rs rename to app/src/json/area.rs index c369da6..4ad9905 100644 --- a/src/json/area.rs +++ b/app/src/json/area.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::{circle::Circle, description::Description, polygon::Polygon, region::Region}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Area { pub description: Description, diff --git a/src/json/circle.rs b/app/src/json/circle.rs similarity index 80% rename from src/json/circle.rs rename to app/src/json/circle.rs index d938893..63c14ab 100644 --- a/src/json/circle.rs +++ b/app/src/json/circle.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::coordinate::Coordinate; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Circle { pub center_position: Coordinate, diff --git a/src/json/contact.rs b/app/src/json/contact.rs similarity index 77% rename from src/json/contact.rs rename to app/src/json/contact.rs index 5e47222..8461c1f 100644 --- a/src/json/contact.rs +++ b/app/src/json/contact.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Contact { pub contact: String, diff --git a/src/json/coordinate.rs b/app/src/json/coordinate.rs similarity index 80% rename from src/json/coordinate.rs rename to app/src/json/coordinate.rs index 39ab09f..41bd26d 100644 --- a/src/json/coordinate.rs +++ b/app/src/json/coordinate.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Coordinate { #[serde(rename = "0")] diff --git a/src/json/datetime.rs b/app/src/json/datetime.rs similarity index 100% rename from src/json/datetime.rs rename to app/src/json/datetime.rs diff --git a/src/json/description.rs b/app/src/json/description.rs similarity index 78% rename from src/json/description.rs rename to app/src/json/description.rs index 93c5f8c..88a0a25 100644 --- a/src/json/description.rs +++ b/app/src/json/description.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Description { pub description: String, diff --git a/src/json/image.rs b/app/src/json/image.rs similarity index 79% rename from src/json/image.rs rename to app/src/json/image.rs index 92f0125..d5f0125 100644 --- a/src/json/image.rs +++ b/app/src/json/image.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Image { pub uri: String, diff --git a/src/json/image_description.rs b/app/src/json/image_description.rs similarity index 82% rename from src/json/image_description.rs rename to app/src/json/image_description.rs index 04dc4d2..7530def 100644 --- a/src/json/image_description.rs +++ b/app/src/json/image_description.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::description::Description; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ImageDescription { pub index: u64, diff --git a/src/json/image_title.rs b/app/src/json/image_title.rs similarity index 81% rename from src/json/image_title.rs rename to app/src/json/image_title.rs index 2227151..8ddcf40 100644 --- a/src/json/image_title.rs +++ b/app/src/json/image_title.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::title::Title; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ImageTitle { pub index: u64, diff --git a/src/json/link.rs b/app/src/json/link.rs similarity index 74% rename from src/json/link.rs rename to app/src/json/link.rs index 47917cb..30afd7a 100644 --- a/src/json/link.rs +++ b/app/src/json/link.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Link { pub href: String, diff --git a/src/json/polygon.rs b/app/src/json/polygon.rs similarity index 77% rename from src/json/polygon.rs rename to app/src/json/polygon.rs index 9cf169b..aa9dd4d 100644 --- a/src/json/polygon.rs +++ b/app/src/json/polygon.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::coordinate::Coordinate; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Polygon { pub coordinates: Vec, diff --git a/src/json/region.rs b/app/src/json/region.rs similarity index 71% rename from src/json/region.rs rename to app/src/json/region.rs index 8a87ca7..79c51f4 100644 --- a/src/json/region.rs +++ b/app/src/json/region.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Region { pub region: String, diff --git a/src/json/text.rs b/app/src/json/text.rs similarity index 76% rename from src/json/text.rs rename to app/src/json/text.rs index e8f6baf..55f7af1 100644 --- a/src/json/text.rs +++ b/app/src/json/text.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Text { pub text: String, diff --git a/src/json/title.rs b/app/src/json/title.rs similarity index 77% rename from src/json/title.rs rename to app/src/json/title.rs index b441f5d..edcfbdf 100644 --- a/src/json/title.rs +++ b/app/src/json/title.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Title { pub title: String, diff --git a/app/src/lib.rs b/app/src/lib.rs new file mode 100644 index 0000000..7e7b5f7 --- /dev/null +++ b/app/src/lib.rs @@ -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, Error> { + info!("checking alerts"); + + let alerts = alert_swiss::fetch_alerts().await?; + let alerts: Vec = alerts + .list + .into_iter() + .filter(|alert| !alert.identifier.starts_with("TEST-")) + .collect(); + + let new_alerts: Vec = 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?; + } +} diff --git a/src/logging.rs b/app/src/logging.rs similarity index 84% rename from src/logging.rs rename to app/src/logging.rs index 16bfc26..2c615da 100644 --- a/src/logging.rs +++ b/app/src/logging.rs @@ -2,7 +2,7 @@ use tracing::debug; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 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() .with( tracing_subscriber::EnvFilter::try_from_default_env() diff --git a/app/src/main.rs b/app/src/main.rs new file mode 100644 index 0000000..3de8f14 --- /dev/null +++ b/app/src/main.rs @@ -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); + } +} diff --git a/entity/Cargo.toml b/entity/Cargo.toml new file mode 100644 index 0000000..0ef0b8c --- /dev/null +++ b/entity/Cargo.toml @@ -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 } diff --git a/entity/src/alerts.rs b/entity/src/alerts.rs new file mode 100644 index 0000000..c8ecb9c --- /dev/null +++ b/entity/src/alerts.rs @@ -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 {} diff --git a/entity/src/lib.rs b/entity/src/lib.rs new file mode 100644 index 0000000..6b3c717 --- /dev/null +++ b/entity/src/lib.rs @@ -0,0 +1,5 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 + +pub mod prelude; + +pub mod alerts; diff --git a/entity/src/prelude.rs b/entity/src/prelude.rs new file mode 100644 index 0000000..1d1a775 --- /dev/null +++ b/entity/src/prelude.rs @@ -0,0 +1,3 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 + +pub use super::alerts::Entity as Alerts; diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index c8f2a5d..e9eb5e9 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -6,42 +6,28 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Replace the sample below with your own migration scripts - todo!(); - manager .create_table( Table::create() - .table(Post::Table) + .table(Alerts::Table) .if_not_exists() - .col( - ColumnDef::new(Post::Id) - .integer() - .not_null() - .auto_increment() - .primary_key(), - ) - .col(ColumnDef::new(Post::Title).string().not_null()) - .col(ColumnDef::new(Post::Text).string().not_null()) + .col(ColumnDef::new(Alerts::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Alerts::PublishDate).date_time().not_null()) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Replace the sample below with your own migration scripts - todo!(); - manager - .drop_table(Table::drop().table(Post::Table).to_owned()) + .drop_table(Table::drop().table(Alerts::Table).to_owned()) .await } } #[derive(DeriveIden)] -enum Post { +enum Alerts { Table, Id, - Title, - Text, + PublishDate, } diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index aec1092..0000000 --- a/src/lib.rs +++ /dev/null @@ -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 = alerts.list.into_iter().filter(|x| true).collect(); - - debug!("{new_alerts:#?}"); - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 40f7d14..0000000 --- a/src/main.rs +++ /dev/null @@ -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 -}