BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 205 lines 5.9 kB view raw
1use super::error::{AppError, Result}; 2use super::state::AppState; 3use rusqlite::params; 4use serde::Serialize; 5use uuid::Uuid; 6 7#[derive(Debug, Clone, Serialize)] 8#[serde(rename_all = "camelCase")] 9pub struct Column { 10 pub id: String, 11 pub account_did: String, 12 pub kind: String, 13 pub config: String, 14 pub position: i64, 15 pub width: String, 16 pub created_at: String, 17} 18 19pub fn get_columns(account_did: &str, state: &AppState) -> Result<Vec<Column>> { 20 let conn = state.auth_store.lock_connection()?; 21 let mut stmt = conn.prepare( 22 "SELECT id, account_did, kind, config, position, width, created_at 23 FROM columns 24 WHERE account_did = ?1 25 ORDER BY position ASC", 26 )?; 27 28 let rows = stmt.query_map(params![account_did], |row| { 29 Ok(Column { 30 id: row.get(0)?, 31 account_did: row.get(1)?, 32 kind: row.get(2)?, 33 config: row.get(3)?, 34 position: row.get(4)?, 35 width: row.get(5)?, 36 created_at: row.get(6)?, 37 }) 38 })?; 39 40 let mut columns = Vec::new(); 41 for row in rows { 42 columns.push(row?); 43 } 44 Ok(columns) 45} 46 47pub fn add_column( 48 account_did: &str, kind: &str, config: &str, position: Option<u32>, state: &AppState, 49) -> Result<Column> { 50 validate_kind(kind)?; 51 validate_config_json(config)?; 52 53 let conn = state.auth_store.lock_connection()?; 54 55 let insert_position = match position { 56 Some(pos) => { 57 conn.execute( 58 "UPDATE columns SET position = position + 1 59 WHERE account_did = ?1 AND position >= ?2", 60 params![account_did, pos], 61 )?; 62 pos as i64 63 } 64 None => { 65 let max: Option<i64> = conn 66 .query_row( 67 "SELECT MAX(position) FROM columns WHERE account_did = ?1", 68 params![account_did], 69 |row| row.get(0), 70 ) 71 .unwrap_or(None); 72 max.map(|m| m + 1).unwrap_or(0) 73 } 74 }; 75 76 let id = Uuid::new_v4().to_string(); 77 conn.execute( 78 "INSERT INTO columns(id, account_did, kind, config, position, width) 79 VALUES (?1, ?2, ?3, ?4, ?5, 'standard')", 80 params![id, account_did, kind, config, insert_position], 81 )?; 82 83 let column = conn.query_row( 84 "SELECT id, account_did, kind, config, position, width, created_at 85 FROM columns WHERE id = ?1", 86 params![id], 87 |row| { 88 Ok(Column { 89 id: row.get(0)?, 90 account_did: row.get(1)?, 91 kind: row.get(2)?, 92 config: row.get(3)?, 93 position: row.get(4)?, 94 width: row.get(5)?, 95 created_at: row.get(6)?, 96 }) 97 }, 98 )?; 99 100 Ok(column) 101} 102 103pub fn remove_column(id: &str, state: &AppState) -> Result<()> { 104 let conn = state.auth_store.lock_connection()?; 105 106 let affected = conn.execute("DELETE FROM columns WHERE id = ?1", params![id])?; 107 108 if affected == 0 { 109 return Err(AppError::validation(format!("column '{id}' not found"))); 110 } 111 112 Ok(()) 113} 114 115pub fn reorder_columns(ids: &[String], state: &AppState) -> Result<()> { 116 if ids.is_empty() { 117 return Ok(()); 118 } 119 120 let conn = state.auth_store.lock_connection()?; 121 122 for (position, id) in ids.iter().enumerate() { 123 conn.execute( 124 "UPDATE columns SET position = ?1 WHERE id = ?2", 125 params![position as i64, id], 126 )?; 127 } 128 129 Ok(()) 130} 131 132pub fn update_column(id: &str, config: Option<&str>, width: Option<&str>, state: &AppState) -> Result<Column> { 133 if config.is_none() && width.is_none() { 134 return Err(AppError::validation("at least one of config or width must be provided")); 135 } 136 137 if let Some(c) = config { 138 validate_config_json(c)?; 139 } 140 141 if let Some(w) = width { 142 validate_width(w)?; 143 } 144 145 let conn = state.auth_store.lock_connection()?; 146 147 let exists: bool = conn 148 .query_row("SELECT 1 FROM columns WHERE id = ?1", params![id], |_| Ok(true)) 149 .unwrap_or(false); 150 151 if !exists { 152 return Err(AppError::validation(format!("column '{id}' not found"))); 153 } 154 155 if let Some(c) = config { 156 conn.execute("UPDATE columns SET config = ?1 WHERE id = ?2", params![c, id])?; 157 } 158 159 if let Some(w) = width { 160 conn.execute("UPDATE columns SET width = ?1 WHERE id = ?2", params![w, id])?; 161 } 162 163 let column = conn.query_row( 164 "SELECT id, account_did, kind, config, position, width, created_at 165 FROM columns WHERE id = ?1", 166 params![id], 167 |row| { 168 Ok(Column { 169 id: row.get(0)?, 170 account_did: row.get(1)?, 171 kind: row.get(2)?, 172 config: row.get(3)?, 173 position: row.get(4)?, 174 width: row.get(5)?, 175 created_at: row.get(6)?, 176 }) 177 }, 178 )?; 179 180 Ok(column) 181} 182 183fn validate_kind(kind: &str) -> Result<()> { 184 match kind { 185 "feed" | "explorer" | "diagnostics" | "messages" | "search" | "profile" => Ok(()), 186 _ => Err(AppError::validation(format!( 187 "invalid column kind '{kind}': must be 'feed', 'explorer', 'diagnostics', 'messages', 'search', or 'profile'" 188 ))), 189 } 190} 191 192fn validate_width(width: &str) -> Result<()> { 193 match width { 194 "narrow" | "standard" | "wide" => Ok(()), 195 _ => Err(AppError::validation(format!( 196 "invalid column width '{width}': must be 'narrow', 'standard', or 'wide'" 197 ))), 198 } 199} 200 201fn validate_config_json(config: &str) -> Result<()> { 202 serde_json::from_str::<serde_json::Value>(config) 203 .map(|_| ()) 204 .map_err(|e| AppError::validation(format!("config must be valid JSON: {e}"))) 205}