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, } pub struct NtfySettingsInput { 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 SmtpSettingsInput { 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, input: NtfySettingsInput) -> Result { sqlx::query_as::<_, NtfySettings>( "INSERT INTO user_ntfy_settings (user_id, enabled, topic, server_url, priority, title_template, message_template) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *" ) .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 } pub async fn update( &self, id: i64, input: NtfySettingsInput, ) -> Result { sqlx::query_as::<_, NtfySettings>( "UPDATE user_ntfy_settings SET enabled = ?, topic = ?, server_url = ?, priority = ?, title_template = ?, message_template = ? WHERE id = ? RETURNING *" ) .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 } 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, input: SmtpSettingsInput) -> 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(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 } pub async fn update( &self, id: i64, input: SmtpSettingsInput, ) -> 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(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 } 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::{Executor, SqlitePool}; 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(NtfySettingsInput { user_id, 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(); 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(NtfySettingsInput { user_id, 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, 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!(!updated.enabled); 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(NtfySettingsInput { user_id, 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(); 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(SmtpSettingsInput { user_id, 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(); 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!(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())); 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(SmtpSettingsInput { user_id, 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, 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!(!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!(!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())); 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(SmtpSettingsInput { user_id, 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(); let fetched = repo.get_by_user(user_id).await.unwrap(); assert!(fetched.is_none()); } }