A Signal's own icon, from all of Font Awesome Free, searchable (#261)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,10 @@ async fn create_missing(orm: &sea_orm::DatabaseConnection) -> Result<()> {
|
||||
orm.execute_unprepared(sql).await.with_context(|| sql.to_owned())?;
|
||||
}
|
||||
}
|
||||
if orm.execute_unprepared("SELECT icon FROM signals LIMIT 0").await.is_err() {
|
||||
let sql = "ALTER TABLE signals ADD COLUMN icon TEXT";
|
||||
orm.execute_unprepared(sql).await.with_context(|| sql.to_owned())?;
|
||||
}
|
||||
if orm.execute_unprepared("SELECT pause_ms FROM deadair_prefs LIMIT 0").await.is_err() {
|
||||
let sql = "ALTER TABLE deadair_prefs ADD COLUMN pause_ms BIGINT";
|
||||
orm.execute_unprepared(sql).await.with_context(|| sql.to_owned())?;
|
||||
@@ -2547,9 +2551,19 @@ pub struct SignalRow {
|
||||
pub name: String,
|
||||
pub keywords: Vec<String>,
|
||||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub unread: i64,
|
||||
}
|
||||
|
||||
/// What a Signal is changed to: each field None to leave it as it is.
|
||||
#[derive(Default)]
|
||||
pub struct SignalChange<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub words: Option<&'a [String]>,
|
||||
pub description: Option<Option<&'a str>>,
|
||||
pub icon: Option<Option<&'a str>>,
|
||||
}
|
||||
|
||||
/// One Signal's question about an item: what it is called, its words, and its owner's sentence.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ask {
|
||||
@@ -2591,7 +2605,7 @@ impl Db {
|
||||
let user = a.p(user_id);
|
||||
let one = id.map(|i| format!("AND g.id = {}", a.p(i))).unwrap_or_default();
|
||||
let sql = format!(
|
||||
"SELECT g.id, g.name, g.keywords, g.description,
|
||||
"SELECT g.id, g.name, g.keywords, g.description, g.icon,
|
||||
(SELECT count(*) FROM signal_verdicts v
|
||||
JOIN entries e ON e.feed_id = v.feed_id AND e.guid = v.guid
|
||||
JOIN subscriptions s ON s.user_id = g.user_id AND s.feed_id = v.feed_id
|
||||
@@ -2609,6 +2623,7 @@ impl Db {
|
||||
name: r.try_get("", "name")?,
|
||||
keywords: keywords(r.try_get("", "keywords")?).unwrap_or_default(),
|
||||
description: r.try_get("", "description")?,
|
||||
icon: r.try_get("", "icon")?,
|
||||
unread: r.try_get("", "unread")?,
|
||||
})
|
||||
})
|
||||
@@ -2629,17 +2644,11 @@ impl Db {
|
||||
Ok(m.id)
|
||||
}
|
||||
|
||||
/// Renames a Signal, or gives it new words or a new sentence. Either of those puts every item
|
||||
/// to Jev again, since what it said was about the old ones; a new name does too, since the
|
||||
/// question is about the name (#259). False for a Signal that is not this person's.
|
||||
pub async fn set_signal(
|
||||
&self,
|
||||
user_id: i64,
|
||||
id: i64,
|
||||
name: Option<&str>,
|
||||
words: Option<&[String]>,
|
||||
description: Option<Option<&str>>,
|
||||
) -> Result<bool> {
|
||||
/// Changes a Signal. A new name, words or sentence puts every item to Jev again, since what it
|
||||
/// said was about the old question (#259); a new icon does not. False for a Signal that is not
|
||||
/// this person's.
|
||||
pub async fn set_signal(&self, user_id: i64, id: i64, change: SignalChange<'_>) -> Result<bool> {
|
||||
let SignalChange { name, words, description, icon } = change;
|
||||
if self.signal(user_id, id).await?.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -2653,8 +2662,13 @@ impl Db {
|
||||
if let Some(d) = description {
|
||||
m.description = Set(d.map(str::to_owned));
|
||||
}
|
||||
if let Some(i) = icon {
|
||||
m.icon = Set(i.map(str::to_owned));
|
||||
}
|
||||
if name.is_some() || words.is_some() || description.is_some() {
|
||||
signal_verdicts::Entity::delete_many().filter(signal_verdicts::Column::SignalId.eq(id)).exec(&self.orm).await?;
|
||||
}
|
||||
if name.is_some() || words.is_some() || description.is_some() || icon.is_some() {
|
||||
m.update(&self.orm).await?;
|
||||
}
|
||||
Ok(true)
|
||||
@@ -3396,15 +3410,19 @@ mod tests {
|
||||
assert_eq!(db.signal(1, f1).await.unwrap().unwrap().unread, 0, "read in its feed is read in the Signal");
|
||||
|
||||
assert!(db.signal(2, f1).await.unwrap().is_none(), "someone else's Signal is not found");
|
||||
assert!(!db.set_signal(2, f1, Some("mine"), None, None).await.unwrap());
|
||||
assert!(!db.set_signal(2, f1, SignalChange { name: Some("mine"), ..Default::default() }).await.unwrap());
|
||||
assert!(!db.remove_signal(2, f1).await.unwrap());
|
||||
|
||||
db.set_signal(1, f1, None, Some(&words(&["f1"])), None).await.unwrap();
|
||||
db.set_signal(1, f1, SignalChange { words: Some(&words(&["f1"])), ..Default::default() }).await.unwrap();
|
||||
assert_eq!(db.signal_count(1, f1, Filter::All, None).await.unwrap(), 0, "new keywords are judged afresh");
|
||||
db.record_verdicts("f", "x", &[(f1, true)]).await.unwrap();
|
||||
db.set_signal(1, f1, None, None, Some(Some("Grand prix racing"))).await.unwrap();
|
||||
db.set_signal(1, f1, SignalChange { description: Some(Some("Grand prix racing")), ..Default::default() }).await.unwrap();
|
||||
assert_eq!(db.signal_count(1, f1, Filter::All, None).await.unwrap(), 0, "so is a new sentence");
|
||||
assert_eq!(db.signal(1, f1).await.unwrap().unwrap().description.as_deref(), Some("Grand prix racing"));
|
||||
db.record_verdicts("f", "x", &[(f1, true)]).await.unwrap();
|
||||
db.set_signal(1, f1, SignalChange { icon: Some(Some("solid/flag-checkered")), ..Default::default() }).await.unwrap();
|
||||
assert_eq!(db.signal_count(1, f1, Filter::All, None).await.unwrap(), 1, "a new icon asks nothing again");
|
||||
assert_eq!(db.signal(1, f1).await.unwrap().unwrap().icon.as_deref(), Some("solid/flag-checkered"));
|
||||
assert!(db.remove_signal(1, food).await.unwrap());
|
||||
db.record_verdicts("f", "x", &[(food, true)]).await.unwrap();
|
||||
assert_eq!(db.signals(1).await.unwrap().len(), 1, "a verdict for a removed Signal is not kept");
|
||||
|
||||
Reference in New Issue
Block a user