This commit is contained in:
Sebastian Hugentobler 2024-05-08 18:12:49 +02:00
commit adda135743
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
27 changed files with 4002 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
result

3339
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "alert-me"
version = "0.1.0"
edition = "2021"
[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" ] }
serde = "1.0.199"
serde_json = "1.0.116"
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

145
flake.lock Normal file
View File

@ -0,0 +1,145 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1714199028,
"narHash": "sha256-QO3Yv3UfJRfhZE1wsHOartg+k8/Kf1BiDyfl8eEpqcE=",
"owner": "nix-community",
"repo": "fenix",
"rev": "055f6db376eaf544d84aa55bd5a7c70634af41ba",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1713520724,
"narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=",
"owner": "nix-community",
"repo": "naersk",
"rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1714076141,
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 0,
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
"path": "/nix/store/370qy3d3wg9zhbn5a3dcv6k1q1iigfh4-source",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1714076141,
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs_3"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1714150666,
"narHash": "sha256-S8AsTyJvT6Q3pRFeo8QepWF/husnJh61cOhRt18Xmyc=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "1ed7e2de05ee76f6ae83cc9c72eb0b33ad6903f2",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

148
flake.nix Normal file
View File

@ -0,0 +1,148 @@
{
description = "alert-me project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
fenix.url = "github:nix-community/fenix";
};
outputs =
{
self,
nixpkgs,
naersk,
fenix,
flake-utils,
...
}:
let
buildTargets = {
"x86_64-linux" = {
crossSystemConfig = "x86_64-unknown-linux-musl";
rustTarget = "x86_64-unknown-linux-musl";
};
"i686-linux" = {
crossSystemConfig = "i686-unknown-linux-musl";
rustTarget = "i686-unknown-linux-musl";
};
"aarch64-linux" = {
crossSystemConfig = "aarch64-unknown-linux-musl";
rustTarget = "aarch64-unknown-linux-musl";
};
"armv6l-linux" = {
crossSystemConfig = "armv6l-unknown-linux-musleabihf";
rustTarget = "arm-unknown-linux-musleabihf";
};
};
eachSystem =
supportedSystems: callback:
builtins.foldl' (overall: system: overall // { ${system} = callback system; }) { } supportedSystems;
eachCrossSystem =
supportedSystems: callback:
eachSystem supportedSystems (
buildSystem:
builtins.foldl' (
inner: targetSystem: inner // { "cross-${targetSystem}" = callback buildSystem targetSystem; }
) { default = callback buildSystem buildSystem; } supportedSystems
);
mkPkgs =
buildSystem: targetSystem:
import nixpkgs (
{
system = buildSystem;
}
// (
if targetSystem == null then
{ }
else
{ crossSystem.config = buildTargets.${targetSystem}.crossSystemConfig; }
)
);
in
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
rust = fenix.packages.${system}.stable;
in
with pkgs;
{
devShells.default = mkShell {
buildInputs = [
mosquitto
rust.toolchain
rust-analyzer
sea-orm-cli
];
};
}
)
// {
packages = eachCrossSystem (builtins.attrNames buildTargets) (
buildSystem: targetSystem:
let
pkgs = mkPkgs buildSystem null;
pkgsCross = mkPkgs buildSystem targetSystem;
rustTarget = buildTargets.${targetSystem}.rustTarget;
fenixPkgs = fenix.packages.${buildSystem};
mkToolchain = fenixPkgs: fenixPkgs.stable;
toolchain = fenixPkgs.combine [
(mkToolchain fenixPkgs).rustc
(mkToolchain fenixPkgs).cargo
(mkToolchain fenixPkgs.targets.${rustTarget}).rust-std
];
buildPackageAttrs =
if builtins.hasAttr "makeBuildPackageAttrs" buildTargets.${targetSystem} then
buildTargets.${targetSystem}.makeBuildPackageAttrs pkgsCross
else
{ };
naersk-lib = pkgs.callPackage naersk {
cargo = toolchain;
rustc = toolchain;
};
in
naersk-lib.buildPackage (
buildPackageAttrs
// rec {
src = ./.;
strictDeps = true;
doCheck = false;
OPENSSL_STATIC = "1";
OPENSSL_LIB_DIR = "${pkgsCross.pkgsStatic.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgsCross.pkgsStatic.openssl.dev}/include";
# Required because ring crate is special. This also seems to have
# fixed some issues with the x86_64-windows cross-compile :shrug:
TARGET_CC = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc";
CARGO_BUILD_TARGET = rustTarget;
CARGO_BUILD_RUSTFLAGS = [
"-C"
"target-feature=+crt-static"
# -latomic is required to build openssl-sys for armv6l-linux, but
# it doesn't seem to hurt any other builds.
# "-C"
# "link-args=-static -latomic"
"-C"
"linker=${TARGET_CC}"
];
}
)
);
};
}

21
migration/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
[dependencies.sea-orm-migration]
version = "0.12.0"
features = [
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
# e.g.
"runtime-tokio-rustls", "sqlx-postgres",
]

41
migration/README.md Normal file
View File

@ -0,0 +1,41 @@
# Running Migrator CLI
- Generate a new migration file
```sh
cargo run -- generate MIGRATION_NAME
```
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

12
migration/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_create_table::Migration)]
}
}

View File

@ -0,0 +1,47 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
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)
.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())
.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())
.await
}
}
#[derive(DeriveIden)]
enum Post {
Table,
Id,
Title,
Text,
}

6
migration/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}

37
src/json/alert.rs Normal file
View File

@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use super::{
area::Area, contact::Contact, description::Description, image::Image,
image_description::ImageDescription, image_title::ImageTitle, link::Link, text::Text,
title::Title,
};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Alert {
pub identifier: String,
pub gtrans_origin: Option<String>,
pub sent: String,
pub title: Title,
pub description: Description,
pub nation_wide: bool,
pub banner: bool,
pub instructions: Vec<Text>,
pub sender: String,
pub publisher_name: Option<String>,
pub link: Option<String>,
pub links: Vec<Link>,
pub contact: Contact,
pub event: String,
pub all_clear: bool,
pub severity: String,
pub test_alert: bool,
pub technical_test_alert: bool,
pub publish_date: String,
pub areas: Vec<Area>,
pub images: Vec<Image>,
pub image_titles: Vec<ImageTitle>,
pub image_descriptions: Vec<ImageDescription>,
pub event_icon_path: String,
pub reference: String,
}

10
src/json/alerts.rs Normal file
View File

@ -0,0 +1,10 @@
use super::alert::Alert;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Alerts {
pub heartbeat_age_in_millis: u64,
pub render_time: String,
pub alerts: Vec<Alert>,
}

13
src/json/area.rs Normal file
View File

@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
use super::{circle::Circle, description::Description, polygon::Polygon, region::Region};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Area {
pub description: Description,
pub region_type: String,
pub polygons: Vec<Polygon>,
pub circles: Vec<Circle>,
pub regions: Vec<Region>,
}

10
src/json/circle.rs Normal file
View File

@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
use super::coordinate::Coordinate;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Circle {
pub center_position: Coordinate,
pub radius: String,
}

8
src/json/contact.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contact {
pub contact: String,
pub contact_origin: Option<String>,
}

10
src/json/coordinate.rs Normal file
View File

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

8
src/json/description.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Description {
pub description: String,
pub description_gtrans_origin: Option<String>,
}

10
src/json/image.rs Normal file
View File

@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Image {
pub uri: String,
pub digest: String,
pub index: u64,
pub size: String,
}

View File

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

12
src/json/image_title.rs Normal file
View File

@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
use super::title::Title;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageTitle {
pub index: u64,
#[serde(flatten)]
pub title: Title,
pub alt_text: String,
}

8
src/json/link.rs Normal file
View File

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

9
src/json/polygon.rs Normal file
View File

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

7
src/json/region.rs Normal file
View File

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

8
src/json/text.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Text {
pub text: String,
pub text_origin: Option<String>,
}

8
src/json/title.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Title {
pub title: String,
pub title_gtrans_origin: Option<String>,
}

14
src/logging.rs Normal file
View File

@ -0,0 +1,14 @@
use tracing::debug;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
pub(crate) fn setup(bin_name: &str) {
let default_config = format!("{}=info,tower_http=info", bin_name);
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| default_config.into()),
)
.with(tracing_subscriber::fmt::layer().with_target(true))
.init();
debug!("tracing/logging is setup");
}

42
src/main.rs Normal file
View File

@ -0,0 +1,42 @@
use std::time::Duration;
use tokio::time;
use tracing::debug;
use crate::json::alerts::Alerts;
mod json {
pub mod alert;
pub mod alerts;
pub mod area;
pub mod circle;
pub mod contact;
pub mod coordinate;
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;
}
mod logging;
#[tokio::main]
async fn main() {
logging::setup("alert-me");
let mut interval = time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
let resp = reqwest::get("https://www.alert.swiss/content/alertswiss-internet/en/home/_jcr_content/polyalert.alertswiss_alerts.actual.json")
.await
.unwrap()
.json::<Alerts>()
.await.unwrap();
debug!("{resp:#?}");
}
}