diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-14 20:52:55 +0300 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-14 20:52:55 +0300 |
| commit | eb0c5d947a2e2755fac4a9b34d9dee6c2987afbb (patch) | |
| tree | 6c423fe456a3cee24e292ee24b609b08dc6704e4 | |
| parent | 1c2873b3059f3e4d6bd02307ec5b22f761ce1c80 (diff) | |
feat: Add dockerfile and docker-compose
| -rw-r--r-- | .woodpecker/elixir-test.yml | 22 | ||||
| -rw-r--r-- | .woodpecker/lint.yml | 17 | ||||
| -rw-r--r-- | .woodpecker/rust-test.yml | 12 | ||||
| -rw-r--r-- | Dockerfile | 18 | ||||
| -rw-r--r-- | docker-compose.yml | 13 | ||||
| -rw-r--r-- | lefthook.yml | 4 | ||||
| -rw-r--r-- | src/lib.rs | 105 | ||||
| -rw-r--r-- | src/main.rs | 26 | ||||
| -rw-r--r-- | src/notifications.rs | 337 | ||||
| -rw-r--r-- | src/weather_poller.rs | 7 | ||||
| -rw-r--r-- | src/weather_thresholds.rs | 57 |
11 files changed, 337 insertions, 281 deletions
diff --git a/.woodpecker/elixir-test.yml b/.woodpecker/elixir-test.yml deleted file mode 100644 index 048d4ed..0000000 --- a/.woodpecker/elixir-test.yml +++ /dev/null @@ -1,22 +0,0 @@ -when: - event: pull_request -steps: - - name: test - image: hexpm/elixir:1.18.3-erlang-25.0.4-debian-bookworm-20250317-slim - commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get --force - - mix compile - - MIX_ENV=test mix ecto.create - - MIX_ENV=test mix ecto.migrate - - MIX_ENV=test mix test - environment: - DB_ADAPTER: sqlite - DATABASE_URL: 'sqlite3:/tmp/silmataivas_test.db' - MIX_ENV: test - when: - event: [pull_request] - -depends_on: - - lint diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml index bb320fe..a32ae83 100644 --- a/.woodpecker/lint.yml +++ b/.woodpecker/lint.yml @@ -26,13 +26,18 @@ steps: event: - pull_request - - name: elixir-format - image: hexpm/elixir:1.18.3-erlang-25.0.4-debian-bookworm-20250317-slim + - name: rust-fmt + image: rust:1.76 commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix format --check-formatted + - cargo fmt --all -- --check + when: + event: + - pull_request + + - name: rust-clippy + image: rust:1.76 + commands: + - cargo clippy --all-targets --all-features -- -D warnings when: event: - pull_request diff --git a/.woodpecker/rust-test.yml b/.woodpecker/rust-test.yml new file mode 100644 index 0000000..ba9d16a --- /dev/null +++ b/.woodpecker/rust-test.yml @@ -0,0 +1,12 @@ +when: + event: pull_request +steps: + - name: test + image: rust:1.76 + commands: + - cargo test --all + when: + event: [pull_request] + +depends_on: + - lint diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..60b6585 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# syntax=docker/dockerfile:1 + +FROM rust:1.76-slim as builder +WORKDIR /app +COPY . . +RUN apt-get update && apt-get install -y pkg-config libssl-dev sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* +RUN cargo build --release + +FROM debian:bookworm-slim +WORKDIR /app +RUN apt-get update && apt-get install -y sqlite3 libsqlite3-0 ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/silmataivas /usr/local/bin/silmataivas +COPY migrations ./migrations +RUN mkdir -p /data && useradd -m appuser +USER appuser +EXPOSE 4000 +ENV DATABASE_URL=sqlite:///data/silmataivas.db +CMD ["/usr/local/bin/silmataivas"]
\ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a82cc8f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' +services: + silmataivas: + build: . + image: silmataivas:latest + container_name: silmataivas + ports: + - "4000:4000" + environment: + - DATABASE_URL=sqlite:///data/silmataivas.db + volumes: + - ./data:/data + restart: unless-stopped
\ No newline at end of file diff --git a/lefthook.yml b/lefthook.yml index 8899132..cabea2b 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -13,7 +13,7 @@ pre-commit: fmt: run: cargo fmt --all -- --check stage_fixed: true - # clippy: - # run: cargo clippy --all-targets --all-features -- -D warnings + clippy: + run: cargo clippy --all-targets --all-features -- -D warnings test: run: cargo test --all
\ No newline at end of file @@ -10,6 +10,9 @@ use sqlx::SqlitePool; mod auth; +use crate::notifications::{NtfySettingsInput, SmtpSettingsInput}; +use crate::weather_thresholds::WeatherThresholdUpdateInput; + fn error_response(status: StatusCode, message: &str) -> axum::response::Response { (status, Json(json!({"error": message}))).into_response() } @@ -265,15 +268,15 @@ mod thresholds_api { Json(payload): Json<UpdateThreshold>, ) -> Result<Json<WeatherThreshold>, String> { let repo = WeatherThresholdRepository { db: &pool }; - repo.update_threshold( + repo.update_threshold(WeatherThresholdUpdateInput { id, - payload.user_id, - payload.condition_type, - payload.threshold_value, - payload.operator, - payload.enabled, - payload.description, - ) + user_id: payload.user_id, + condition_type: payload.condition_type, + threshold_value: payload.threshold_value, + operator: payload.operator, + enabled: payload.enabled, + description: payload.description, + }) .await .map(Json) .map_err(|e| e.to_string()) @@ -342,15 +345,15 @@ mod notifications_api { Json(payload): Json<CreateNtfy>, ) -> Result<Json<NtfySettings>, String> { let repo = NtfySettingsRepository { db: &pool }; - repo.create( - payload.user_id, - payload.enabled, - payload.topic, - payload.server_url, - payload.priority, - payload.title_template, - payload.message_template, - ) + repo.create(NtfySettingsInput { + user_id: payload.user_id, + enabled: payload.enabled, + topic: payload.topic, + server_url: payload.server_url, + priority: payload.priority, + title_template: payload.title_template, + message_template: payload.message_template, + }) .await .map(Json) .map_err(|e| e.to_string()) @@ -364,12 +367,15 @@ mod notifications_api { let repo = NtfySettingsRepository { db: &pool }; repo.update( id, - payload.enabled, - payload.topic, - payload.server_url, - payload.priority, - payload.title_template, - payload.message_template, + NtfySettingsInput { + user_id: 0, // user_id is not updated here, but struct requires it + enabled: payload.enabled, + topic: payload.topic, + server_url: payload.server_url, + priority: payload.priority, + title_template: payload.title_template, + message_template: payload.message_template, + }, ) .await .map(Json) @@ -432,20 +438,20 @@ mod notifications_api { Json(payload): Json<CreateSmtp>, ) -> Result<Json<SmtpSettings>, String> { let repo = SmtpSettingsRepository { db: &pool }; - repo.create( - payload.user_id, - payload.enabled, - payload.email, - payload.smtp_server, - payload.smtp_port, - payload.username, - payload.password, - payload.use_tls, - payload.from_email, - payload.from_name, - payload.subject_template, - payload.body_template, - ) + repo.create(SmtpSettingsInput { + user_id: payload.user_id, + enabled: payload.enabled, + email: payload.email, + smtp_server: payload.smtp_server, + smtp_port: payload.smtp_port, + username: payload.username, + password: payload.password, + use_tls: payload.use_tls, + from_email: payload.from_email, + from_name: payload.from_name, + subject_template: payload.subject_template, + body_template: payload.body_template, + }) .await .map(Json) .map_err(|e| e.to_string()) @@ -459,17 +465,20 @@ mod notifications_api { let repo = SmtpSettingsRepository { db: &pool }; repo.update( id, - payload.enabled, - payload.email, - payload.smtp_server, - payload.smtp_port, - payload.username, - payload.password, - payload.use_tls, - payload.from_email, - payload.from_name, - payload.subject_template, - payload.body_template, + SmtpSettingsInput { + user_id: 0, // user_id is not updated here, but struct requires it + enabled: payload.enabled, + email: payload.email, + smtp_server: payload.smtp_server, + smtp_port: payload.smtp_port, + username: payload.username, + password: payload.password, + use_tls: payload.use_tls, + from_email: payload.from_email, + from_name: payload.from_name, + subject_template: payload.subject_template, + body_template: payload.body_template, + }, ) .await .map(Json) diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a387657 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,26 @@ +use silmataivas::app_with_state; +use sqlx::SqlitePool; +use std::env; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::fs; + +#[tokio::main] +async fn main() { + // Set up database path + let db_path = + env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://./data/silmataivas.db".to_string()); + // Ensure data directory exists + let _ = fs::create_dir_all("./data").await; + // Connect to SQLite + let pool = SqlitePool::connect(&db_path) + .await + .expect("Failed to connect to DB"); + let app = app_with_state(Arc::new(pool)); + let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); + let listener = tokio::net::TcpListener::bind(addr) + .await + .expect("Failed to bind address"); + println!("Listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, app).await.unwrap(); +} diff --git a/src/notifications.rs b/src/notifications.rs index 92d24d2..f725c26 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -13,6 +13,16 @@ pub struct NtfySettings { pub message_template: Option<String>, } +pub struct NtfySettingsInput { + pub user_id: i64, + pub enabled: bool, + pub topic: String, + pub server_url: String, + pub priority: i32, + pub title_template: Option<String>, + pub message_template: Option<String>, +} + #[derive(Debug, Serialize, Deserialize, FromRow, Clone, PartialEq)] pub struct SmtpSettings { pub id: i64, @@ -30,6 +40,21 @@ pub struct SmtpSettings { pub body_template: Option<String>, } +pub struct SmtpSettingsInput { + pub user_id: i64, + pub enabled: bool, + pub email: String, + pub smtp_server: String, + pub smtp_port: i32, + pub username: Option<String>, + pub password: Option<String>, + pub use_tls: bool, + pub from_email: Option<String>, + pub from_name: Option<String>, + pub subject_template: Option<String>, + pub body_template: Option<String>, +} + pub struct NtfySettingsRepository<'a> { pub db: &'a sqlx::SqlitePool, } @@ -42,26 +67,17 @@ impl<'a> NtfySettingsRepository<'a> { .await } - pub async fn create( - &self, - user_id: i64, - enabled: bool, - topic: String, - server_url: String, - priority: i32, - title_template: Option<String>, - message_template: Option<String>, - ) -> Result<NtfySettings, sqlx::Error> { + pub async fn create(&self, input: NtfySettingsInput) -> Result<NtfySettings, sqlx::Error> { sqlx::query_as::<_, NtfySettings>( "INSERT INTO user_ntfy_settings (user_id, enabled, topic, server_url, priority, title_template, message_template) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *" ) - .bind(user_id) - .bind(enabled) - .bind(topic) - .bind(server_url) - .bind(priority) - .bind(title_template) - .bind(message_template) + .bind(input.user_id) + .bind(input.enabled) + .bind(input.topic) + .bind(input.server_url) + .bind(input.priority) + .bind(input.title_template) + .bind(input.message_template) .fetch_one(self.db) .await } @@ -69,22 +85,17 @@ impl<'a> NtfySettingsRepository<'a> { pub async fn update( &self, id: i64, - enabled: bool, - topic: String, - server_url: String, - priority: i32, - title_template: Option<String>, - message_template: Option<String>, + input: NtfySettingsInput, ) -> Result<NtfySettings, sqlx::Error> { sqlx::query_as::<_, NtfySettings>( "UPDATE user_ntfy_settings SET enabled = ?, topic = ?, server_url = ?, priority = ?, title_template = ?, message_template = ? WHERE id = ? RETURNING *" ) - .bind(enabled) - .bind(topic) - .bind(server_url) - .bind(priority) - .bind(title_template) - .bind(message_template) + .bind(input.enabled) + .bind(input.topic) + .bind(input.server_url) + .bind(input.priority) + .bind(input.title_template) + .bind(input.message_template) .bind(id) .fetch_one(self.db) .await @@ -111,36 +122,22 @@ impl<'a> SmtpSettingsRepository<'a> { .await } - pub async fn create( - &self, - user_id: i64, - enabled: bool, - email: String, - smtp_server: String, - smtp_port: i32, - username: Option<String>, - password: Option<String>, - use_tls: bool, - from_email: Option<String>, - from_name: Option<String>, - subject_template: Option<String>, - body_template: Option<String>, - ) -> Result<SmtpSettings, sqlx::Error> { + pub async fn create(&self, input: SmtpSettingsInput) -> Result<SmtpSettings, sqlx::Error> { sqlx::query_as::<_, SmtpSettings>( "INSERT INTO user_smtp_settings (user_id, enabled, email, smtp_server, smtp_port, username, password, use_tls, from_email, from_name, subject_template, body_template) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *" ) - .bind(user_id) - .bind(enabled) - .bind(email) - .bind(smtp_server) - .bind(smtp_port) - .bind(username) - .bind(password) - .bind(use_tls) - .bind(from_email) - .bind(from_name) - .bind(subject_template) - .bind(body_template) + .bind(input.user_id) + .bind(input.enabled) + .bind(input.email) + .bind(input.smtp_server) + .bind(input.smtp_port) + .bind(input.username) + .bind(input.password) + .bind(input.use_tls) + .bind(input.from_email) + .bind(input.from_name) + .bind(input.subject_template) + .bind(input.body_template) .fetch_one(self.db) .await } @@ -148,32 +145,22 @@ impl<'a> SmtpSettingsRepository<'a> { pub async fn update( &self, id: i64, - enabled: bool, - email: String, - smtp_server: String, - smtp_port: i32, - username: Option<String>, - password: Option<String>, - use_tls: bool, - from_email: Option<String>, - from_name: Option<String>, - subject_template: Option<String>, - body_template: Option<String>, + input: SmtpSettingsInput, ) -> Result<SmtpSettings, sqlx::Error> { sqlx::query_as::<_, SmtpSettings>( "UPDATE user_smtp_settings SET enabled = ?, email = ?, smtp_server = ?, smtp_port = ?, username = ?, password = ?, use_tls = ?, from_email = ?, from_name = ?, subject_template = ?, body_template = ? WHERE id = ? RETURNING *" ) - .bind(enabled) - .bind(email) - .bind(smtp_server) - .bind(smtp_port) - .bind(username) - .bind(password) - .bind(use_tls) - .bind(from_email) - .bind(from_name) - .bind(subject_template) - .bind(body_template) + .bind(input.enabled) + .bind(input.email) + .bind(input.smtp_server) + .bind(input.smtp_port) + .bind(input.username) + .bind(input.password) + .bind(input.use_tls) + .bind(input.from_email) + .bind(input.from_name) + .bind(input.subject_template) + .bind(input.body_template) .bind(id) .fetch_one(self.db) .await @@ -255,16 +242,16 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(NtfySettingsInput { user_id, - true, - "topic1".to_string(), - "https://ntfy.sh".to_string(), - 3, - Some("title".to_string()), - Some("msg".to_string()), - ) + enabled: true, + topic: "topic1".to_string(), + server_url: "https://ntfy.sh".to_string(), + priority: 3, + title_template: Some("title".to_string()), + message_template: Some("msg".to_string()), + }) .await .unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap().unwrap(); @@ -280,31 +267,34 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(NtfySettingsInput { user_id, - true, - "topic1".to_string(), - "https://ntfy.sh".to_string(), - 3, - None, - None, - ) + enabled: true, + topic: "topic1".to_string(), + server_url: "https://ntfy.sh".to_string(), + priority: 3, + title_template: None, + message_template: None, + }) .await .unwrap(); let updated = repo .update( - settings.id, - false, - "topic2".to_string(), - "https://ntfy2.sh".to_string(), - 4, - Some("t2".to_string()), - Some("m2".to_string()), + _settings.id, + NtfySettingsInput { + user_id, + enabled: false, + topic: "topic2".to_string(), + server_url: "https://ntfy2.sh".to_string(), + priority: 4, + title_template: Some("t2".to_string()), + message_template: Some("m2".to_string()), + }, ) .await .unwrap(); - assert_eq!(updated.enabled, false); + assert!(!updated.enabled); assert_eq!(updated.topic, "topic2"); assert_eq!(updated.server_url, "https://ntfy2.sh"); assert_eq!(updated.priority, 4); @@ -317,19 +307,19 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(NtfySettingsInput { user_id, - true, - "topic1".to_string(), - "https://ntfy.sh".to_string(), - 3, - None, - None, - ) + enabled: true, + topic: "topic1".to_string(), + server_url: "https://ntfy.sh".to_string(), + priority: 3, + title_template: None, + message_template: None, + }) .await .unwrap(); - repo.delete(settings.id).await.unwrap(); + repo.delete(_settings.id).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap(); assert!(fetched.is_none()); } @@ -339,21 +329,21 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(SmtpSettingsInput { user_id, - true, - "test@example.com".to_string(), - "smtp.example.com".to_string(), - 587, - Some("user".to_string()), - Some("pass".to_string()), - true, - Some("from@example.com".to_string()), - Some("Alerts".to_string()), - Some("subj".to_string()), - Some("body".to_string()), - ) + enabled: true, + email: "test@example.com".to_string(), + smtp_server: "smtp.example.com".to_string(), + smtp_port: 587, + username: Some("user".to_string()), + password: Some("pass".to_string()), + use_tls: true, + from_email: Some("from@example.com".to_string()), + from_name: Some("Alerts".to_string()), + subject_template: Some("subj".to_string()), + body_template: Some("body".to_string()), + }) .await .unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap().unwrap(); @@ -362,7 +352,7 @@ mod tests { assert_eq!(fetched.smtp_port, 587); assert_eq!(fetched.username, Some("user".to_string())); assert_eq!(fetched.password, Some("pass".to_string())); - assert_eq!(fetched.use_tls, true); + assert!(fetched.use_tls); assert_eq!(fetched.from_email, Some("from@example.com".to_string())); assert_eq!(fetched.from_name, Some("Alerts".to_string())); assert_eq!(fetched.subject_template, Some("subj".to_string())); @@ -374,47 +364,50 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(SmtpSettingsInput { user_id, - true, - "test@example.com".to_string(), - "smtp.example.com".to_string(), - 587, - None, - None, - true, - None, - None, - None, - None, - ) + enabled: true, + email: "test@example.com".to_string(), + smtp_server: "smtp.example.com".to_string(), + smtp_port: 587, + username: None, + password: None, + use_tls: true, + from_email: None, + from_name: None, + subject_template: None, + body_template: None, + }) .await .unwrap(); let updated = repo .update( - settings.id, - false, - "other@example.com".to_string(), - "smtp2.example.com".to_string(), - 465, - Some("u2".to_string()), - Some("p2".to_string()), - false, - Some("f2@example.com".to_string()), - Some("N2".to_string()), - Some("s2".to_string()), - Some("b2".to_string()), + _settings.id, + SmtpSettingsInput { + user_id, + enabled: false, + email: "other@example.com".to_string(), + smtp_server: "smtp2.example.com".to_string(), + smtp_port: 465, + username: Some("u2".to_string()), + password: Some("p2".to_string()), + use_tls: false, + from_email: Some("f2@example.com".to_string()), + from_name: Some("N2".to_string()), + subject_template: Some("s2".to_string()), + body_template: Some("b2".to_string()), + }, ) .await .unwrap(); - assert_eq!(updated.enabled, false); + assert!(!updated.enabled); assert_eq!(updated.email, "other@example.com"); assert_eq!(updated.smtp_server, "smtp2.example.com"); assert_eq!(updated.smtp_port, 465); assert_eq!(updated.username, Some("u2".to_string())); assert_eq!(updated.password, Some("p2".to_string())); - assert_eq!(updated.use_tls, false); + assert!(!updated.use_tls); assert_eq!(updated.from_email, Some("f2@example.com".to_string())); assert_eq!(updated.from_name, Some("N2".to_string())); assert_eq!(updated.subject_template, Some("s2".to_string())); @@ -426,24 +419,24 @@ mod tests { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; - let settings = repo - .create( + let _settings = repo + .create(SmtpSettingsInput { user_id, - true, - "test@example.com".to_string(), - "smtp.example.com".to_string(), - 587, - None, - None, - true, - None, - None, - None, - None, - ) + enabled: true, + email: "test@example.com".to_string(), + smtp_server: "smtp.example.com".to_string(), + smtp_port: 587, + username: None, + password: None, + use_tls: true, + from_email: None, + from_name: None, + subject_template: None, + body_template: None, + }) .await .unwrap(); - repo.delete(settings.id).await.unwrap(); + repo.delete(_settings.id).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap(); assert!(fetched.is_none()); } diff --git a/src/weather_poller.rs b/src/weather_poller.rs index 4fd418d..6a32661 100644 --- a/src/weather_poller.rs +++ b/src/weather_poller.rs @@ -163,7 +163,7 @@ async fn send_ntfy_notification( }; let client = Client::new(); let _ = client - .post(&format!("{}/{}", ntfy.server_url, ntfy.topic)) + .post(format!("{}/{}", ntfy.server_url, ntfy.topic)) .header("Priority", ntfy.priority.to_string()) .header("Title", title) .body(message) @@ -202,7 +202,7 @@ async fn send_smtp_notification( .clone() .unwrap_or_else(|| "Silmätaivas Alerts".to_string()); let email = Message::builder() - .from(format!("{} <{}>", from_name, from).parse().unwrap()) + .from(format!("{from_name} <{from}>").parse().unwrap()) .to(smtp.email.parse().unwrap()) .subject(subject) .body(body) @@ -283,8 +283,7 @@ fn default_weather_message(entry: &Value) -> String { .unwrap_or(0.0); let time = entry["dt_txt"].as_str().unwrap_or("N/A"); format!( - "🚨 Weather alert for your location ({}):\n\n🌬️ Wind: {:.1} km/h\n🌧️ Rain: {:.1} mm\n🌡️ Temperature: {:.1} °C\n\nStay safe,\n— Silmätaivas", - time, wind, rain, temp + "🚨 Weather alert for your location ({time}):\n\n🌬️ Wind: {wind:.1} km/h\n🌧️ Rain: {rain:.1} mm\n🌡️ Temperature: {temp:.1} °C\n\nStay safe,\n— Silmätaivas" ) } diff --git a/src/weather_thresholds.rs b/src/weather_thresholds.rs index dadfeda..36237df 100644 --- a/src/weather_thresholds.rs +++ b/src/weather_thresholds.rs @@ -19,6 +19,16 @@ pub struct WeatherThreshold { pub description: Option<String>, } +pub struct WeatherThresholdUpdateInput { + pub id: i64, + pub user_id: i64, + pub condition_type: String, + pub threshold_value: f64, + pub operator: String, + pub enabled: bool, + pub description: Option<String>, +} + pub struct WeatherThresholdRepository<'a> { pub db: &'a sqlx::SqlitePool, } @@ -74,24 +84,18 @@ impl<'a> WeatherThresholdRepository<'a> { pub async fn update_threshold( &self, - id: i64, - user_id: i64, - condition_type: String, - threshold_value: f64, - operator: String, - enabled: bool, - description: Option<String>, + input: WeatherThresholdUpdateInput, ) -> Result<WeatherThreshold, sqlx::Error> { sqlx::query_as::<_, WeatherThreshold>( "UPDATE weather_thresholds SET condition_type = ?, threshold_value = ?, operator = ?, enabled = ?, description = ? WHERE id = ? AND user_id = ? RETURNING id, user_id, condition_type, threshold_value, operator, enabled, description" ) - .bind(condition_type) - .bind(threshold_value) - .bind(operator) - .bind(enabled) - .bind(description) - .bind(id) - .bind(user_id) + .bind(input.condition_type) + .bind(input.threshold_value) + .bind(input.operator) + .bind(input.enabled) + .bind(input.description) + .bind(input.id) + .bind(input.user_id) .fetch_one(self.db) .await } @@ -131,10 +135,9 @@ pub async fn list_thresholds( mod tests { use super::*; use crate::users::{UserRepository, UserRole}; - use axum::extract::{Json, State}; - use hyper::StatusCode; + use sqlx::{Executor, SqlitePool}; - use std::sync::Arc; + use tokio; async fn setup_db() -> SqlitePool { @@ -191,7 +194,7 @@ mod tests { assert_eq!(fetched.condition_type, "wind_speed"); assert_eq!(fetched.threshold_value, 10.0); assert_eq!(fetched.operator, ">"); - assert_eq!(fetched.enabled, true); + assert!(fetched.enabled); assert_eq!(fetched.description, Some("desc".to_string())); } @@ -212,21 +215,21 @@ mod tests { .await .unwrap(); let updated = repo - .update_threshold( - th.id, + .update_threshold(WeatherThresholdUpdateInput { + id: th.id, user_id, - "rain".to_string(), - 5.0, - "<".to_string(), - false, - Some("rain desc".to_string()), - ) + condition_type: "rain".to_string(), + threshold_value: 5.0, + operator: "<".to_string(), + enabled: false, + description: Some("rain desc".to_string()), + }) .await .unwrap(); assert_eq!(updated.condition_type, "rain"); assert_eq!(updated.threshold_value, 5.0); assert_eq!(updated.operator, "<"); - assert_eq!(updated.enabled, false); + assert!(!updated.enabled); assert_eq!(updated.description, Some("rain desc".to_string())); } |
