Explorar o código

Add a metadata field for freeform text

Mark Rousskov %!s(int64=5) %!d(string=hai) anos
pai
achega
53b0eb8629
Modificáronse 3 ficheiros con 120 adicións e 0 borrados
  1. 1 0
      src/db.rs
  2. 63 0
      src/db/notifications.rs
  3. 56 0
      src/zulip.rs

+ 1 - 0
src/db.rs

@@ -70,4 +70,5 @@ CREATE TABLE users (
     "ALTER TABLE notifications ADD COLUMN short_description TEXT;",
     "ALTER TABLE notifications ADD COLUMN team_name TEXT;",
     "ALTER TABLE notifications ADD COLUMN idx INTEGER;",
+    "ALTER TABLE notifications ADD COLUMN metadata TEXT;",
 ];

+ 63 - 0
src/db/notifications.rs

@@ -192,6 +192,69 @@ pub async fn move_indices(
     Ok(())
 }
 
+pub async fn add_metadata(
+    db: &mut DbClient,
+    user_id: i64,
+    idx: usize,
+    metadata: Option<&str>,
+) -> anyhow::Result<()> {
+    loop {
+        let t = db
+            .build_transaction()
+            .isolation_level(tokio_postgres::IsolationLevel::Serializable)
+            .start()
+            .await
+            .context("begin transaction")?;
+
+        let notifications = t
+            .query(
+                "select notification_id, idx, user_id
+        from notifications
+        where user_id = $1
+        order by idx desc nulls last, time desc;",
+                &[&user_id],
+            )
+            .await
+            .context("failed to get initial ordering")?;
+
+        let notifications = notifications
+            .into_iter()
+            .map(|n| n.get(0))
+            .collect::<Vec<i64>>();
+
+        match notifications.get(idx) {
+            None => anyhow::bail!(
+                "index not present, must be less than {}",
+                notifications.len()
+            ),
+            Some(id) => {
+                t.execute(
+                    "update notifications SET idx = $2, metadata = $3
+                 where notification_id = $1",
+                    &[&id, &(idx as i32), &metadata],
+                )
+                .await
+                .context("update notification id")?;
+            }
+        }
+
+        if let Err(e) = t.commit().await {
+            if e.code().map_or(false, |c| {
+                *c == tokio_postgres::error::SqlState::T_R_SERIALIZATION_FAILURE
+            }) {
+                log::trace!("serialization failure, restarting index movement");
+                continue;
+            } else {
+                return Err(e).context("transaction commit failure");
+            }
+        } else {
+            break;
+        }
+    }
+
+    Ok(())
+}
+
 pub async fn get_notifications(
     db: &DbClient,
     username: &str,

+ 56 - 0
src/zulip.rs

@@ -1,3 +1,4 @@
+use crate::db::notifications::add_metadata;
 use crate::db::notifications::{self, delete_ping, move_indices, record_ping, Identifier};
 use crate::github::GithubClient;
 use crate::handlers::Context;
@@ -96,6 +97,16 @@ pub async fn respond(ctx: &Context, req: Request) -> String {
             })
             .unwrap(),
         },
+        Some("meta") => match add_meta_notification(&ctx, gh_id, words).await {
+            Ok(r) => r,
+            Err(e) => serde_json::to_string(&Response {
+                content: &format!(
+                    "Failed to parse movement, expected `move <idx> <meta...>`: {:?}.",
+                    e
+                ),
+            })
+            .unwrap(),
+        },
         _ => serde_json::to_string(&Response {
             content: "Unknown command.",
         })
@@ -186,6 +197,51 @@ async fn add_notification(
         .unwrap()),
     }
 }
+
+async fn add_meta_notification(
+    ctx: &Context,
+    gh_id: i64,
+    mut words: impl Iterator<Item = &str>,
+) -> anyhow::Result<String> {
+    let idx = match words.next() {
+        Some(idx) => idx,
+        None => anyhow::bail!("idx not present"),
+    };
+    let idx = idx
+        .parse::<usize>()
+        .context("index")?
+        .checked_sub(1)
+        .ok_or_else(|| anyhow::anyhow!("1-based indexes"))?;
+    let mut description = words.fold(String::new(), |mut acc, piece| {
+        acc.push_str(piece);
+        acc.push(' ');
+        acc
+    });
+    let description = if description.is_empty() {
+        None
+    } else {
+        assert_eq!(description.pop(), Some(' ')); // pop trailing space
+        Some(description)
+    };
+    match add_metadata(
+        &mut Context::make_db_client(&ctx.github.raw()).await?,
+        gh_id,
+        idx,
+        description.as_deref(),
+    )
+    .await
+    {
+        Ok(()) => Ok(serde_json::to_string(&Response {
+            content: "Added metadata!",
+        })
+        .unwrap()),
+        Err(e) => Ok(serde_json::to_string(&Response {
+            content: &format!("Failed to add: {:?}", e),
+        })
+        .unwrap()),
+    }
+}
+
 async fn move_notification(
     ctx: &Context,
     gh_id: i64,