use serde::{Deserialize, Serialize}; use sqlx::FromRow; #[derive(Debug, Serialize, Deserialize, FromRow, Clone, PartialEq)] pub struct NtfySettings { pub id: i64, pub user_id: i64, pub enabled: bool, pub topic: String, pub server_url: String, pub priority: i32, pub title_template: Option, pub message_template: Option, } #[derive(Debug, Serialize, Deserialize, FromRow, Clone, PartialEq)] pub struct SmtpSettings { pub id: i64, pub user_id: i64, pub enabled: bool, pub email: String, pub smtp_server: String, pub smtp_port: i32, pub username: Option, pub password: Option, pub use_tls: bool, pub from_email: Option, pub from_name: Option, pub subject_template: Option, pub body_template: Option, } pub struct NtfySettingsRepository<'a> { pub db: &'a sqlx::SqlitePool, } impl<'a> NtfySettingsRepository<'a> { pub async fn get_by_user(&self, user_id: i64) -> Result, sqlx::Error> { sqlx::query_as::<_, NtfySettings>( "SELECT * FROM user_ntfy_settings WHERE user_id = ?" ) .bind(user_id) .fetch_optional(self.db) .await } pub async fn create(&self, user_id: i64, enabled: bool, topic: String, server_url: String, priority: i32, title_template: Option, message_template: Option) -> Result { 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) .fetch_one(self.db) .await } pub async fn update(&self, id: i64, enabled: bool, topic: String, server_url: String, priority: i32, title_template: Option, message_template: Option) -> Result { 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(id) .fetch_one(self.db) .await } pub async fn delete(&self, id: i64) -> Result<(), sqlx::Error> { sqlx::query("DELETE FROM user_ntfy_settings WHERE id = ?") .bind(id) .execute(self.db) .await?; Ok(()) } } pub struct SmtpSettingsRepository<'a> { pub db: &'a sqlx::SqlitePool, } impl<'a> SmtpSettingsRepository<'a> { pub async fn get_by_user(&self, user_id: i64) -> Result, sqlx::Error> { sqlx::query_as::<_, SmtpSettings>( "SELECT * FROM user_smtp_settings WHERE user_id = ?" ) .bind(user_id) .fetch_optional(self.db) .await } pub async fn create(&self, user_id: i64, enabled: bool, email: String, smtp_server: String, smtp_port: i32, username: Option, password: Option, use_tls: bool, from_email: Option, from_name: Option, subject_template: Option, body_template: Option) -> Result { 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) .fetch_one(self.db) .await } pub async fn update(&self, id: i64, enabled: bool, email: String, smtp_server: String, smtp_port: i32, username: Option, password: Option, use_tls: bool, from_email: Option, from_name: Option, subject_template: Option, body_template: Option) -> Result { 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(id) .fetch_one(self.db) .await } pub async fn delete(&self, id: i64) -> Result<(), sqlx::Error> { sqlx::query("DELETE FROM user_smtp_settings WHERE id = ?") .bind(id) .execute(self.db) .await?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::users::{UserRepository, UserRole}; use sqlx::{SqlitePool, Executor}; use tokio; async fn setup_db() -> SqlitePool { let pool = SqlitePool::connect(":memory:").await.unwrap(); pool.execute( "CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL UNIQUE, role TEXT NOT NULL DEFAULT 'user' );" ).await.unwrap(); pool.execute( "CREATE TABLE user_ntfy_settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, enabled BOOLEAN NOT NULL DEFAULT 0, topic TEXT NOT NULL, server_url TEXT NOT NULL, priority INTEGER NOT NULL DEFAULT 5, title_template TEXT, message_template TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE );" ).await.unwrap(); pool.execute( "CREATE TABLE user_smtp_settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, enabled BOOLEAN NOT NULL DEFAULT 0, email TEXT NOT NULL, smtp_server TEXT NOT NULL, smtp_port INTEGER NOT NULL, username TEXT, password TEXT, use_tls BOOLEAN NOT NULL DEFAULT 1, from_email TEXT, from_name TEXT DEFAULT 'Silmätaivas Alerts', subject_template TEXT, body_template TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE );" ).await.unwrap(); pool } async fn create_user(pool: &SqlitePool) -> i64 { let repo = UserRepository { db: pool }; let user = repo.create_user(None, Some(UserRole::User)).await.unwrap(); user.id } #[tokio::test] async fn test_create_and_get_ntfy_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; let settings = repo.create(user_id, true, "topic1".to_string(), "https://ntfy.sh".to_string(), 3, Some("title".to_string()), Some("msg".to_string())).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap().unwrap(); assert_eq!(fetched.topic, "topic1"); assert_eq!(fetched.server_url, "https://ntfy.sh"); assert_eq!(fetched.priority, 3); assert_eq!(fetched.title_template, Some("title".to_string())); assert_eq!(fetched.message_template, Some("msg".to_string())); } #[tokio::test] async fn test_update_ntfy_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; let settings = repo.create(user_id, true, "topic1".to_string(), "https://ntfy.sh".to_string(), 3, None, 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())).await.unwrap(); assert_eq!(updated.enabled, false); assert_eq!(updated.topic, "topic2"); assert_eq!(updated.server_url, "https://ntfy2.sh"); assert_eq!(updated.priority, 4); assert_eq!(updated.title_template, Some("t2".to_string())); assert_eq!(updated.message_template, Some("m2".to_string())); } #[tokio::test] async fn test_delete_ntfy_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = NtfySettingsRepository { db: &db }; let settings = repo.create(user_id, true, "topic1".to_string(), "https://ntfy.sh".to_string(), 3, None, None).await.unwrap(); repo.delete(settings.id).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap(); assert!(fetched.is_none()); } #[tokio::test] async fn test_create_and_get_smtp_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; let settings = repo.create(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())).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap().unwrap(); assert_eq!(fetched.email, "test@example.com"); assert_eq!(fetched.smtp_server, "smtp.example.com"); 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_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())); assert_eq!(fetched.body_template, Some("body".to_string())); } #[tokio::test] async fn test_update_smtp_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; let settings = repo.create(user_id, true, "test@example.com".to_string(), "smtp.example.com".to_string(), 587, None, None, true, None, None, None, 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())).await.unwrap(); assert_eq!(updated.enabled, false); 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_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())); assert_eq!(updated.body_template, Some("b2".to_string())); } #[tokio::test] async fn test_delete_smtp_settings() { let db = setup_db().await; let user_id = create_user(&db).await; let repo = SmtpSettingsRepository { db: &db }; let settings = repo.create(user_id, true, "test@example.com".to_string(), "smtp.example.com".to_string(), 587, None, None, true, None, None, None, None).await.unwrap(); repo.delete(settings.id).await.unwrap(); let fetched = repo.get_by_user(user_id).await.unwrap(); assert!(fetched.is_none()); } }