Ver código fonte

Acknowledgement by index via Zulip

Mark Rousskov 5 anos atrás
pai
commit
482eb9b284
3 arquivos alterados com 88 adições e 11 exclusões
  1. 65 8
      src/db/notifications.rs
  2. 7 1
      src/handlers/notification.rs
  3. 16 2
      src/zulip.rs

+ 65 - 8
src/db/notifications.rs

@@ -30,14 +30,71 @@ pub async fn record_ping(db: &DbClient, notification: &Notification) -> anyhow::
     Ok(())
 }
 
-pub async fn delete_ping(db: &DbClient, user_id: i64, origin_url: &str) -> anyhow::Result<()> {
-    db.execute(
-        "DELETE FROM notifications WHERE user_id = $1 and origin_url = $2",
-        &[&user_id, &origin_url],
-    )
-    .await
-    .context("delete notification query")?;
+#[derive(Copy, Clone)]
+pub enum Identifier<'a> {
+    Url(&'a str),
+    Index(std::num::NonZeroUsize),
+}
 
+pub async fn delete_ping(
+    db: &mut DbClient,
+    user_id: i64,
+    identifier: Identifier<'_>,
+) -> anyhow::Result<()> {
+    match identifier {
+        Identifier::Url(origin_url) => {
+            db.execute(
+                "DELETE FROM notifications WHERE user_id = $1 and origin_url = $2",
+                &[&user_id, &origin_url],
+            )
+            .await
+            .context("delete notification query")?;
+        }
+        Identifier::Index(idx) => 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 ordering")?;
+
+            let notification_id: i64 = notifications[idx.get() - 1].get(0);
+
+            t.execute(
+                "DELETE FROM notifications WHERE notification_id = $1",
+                &[&notification_id],
+            )
+            .await
+            .context(format!(
+                "Failed to delete notification with id {}",
+                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 deletion");
+                    continue;
+                } else {
+                    return Err(e).context("transaction commit failure");
+                }
+            } else {
+                break;
+            }
+        },
+    }
     Ok(())
 }
 
@@ -68,7 +125,7 @@ pub async fn move_indices(
                 "select notification_id, idx, user_id
         from notifications
         where user_id = $1
-        order by idx desc, time desc;",
+        order by idx desc nulls last, time desc;",
                 &[&user_id],
             )
             .await

+ 7 - 1
src/handlers/notification.rs

@@ -47,7 +47,13 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
             }
         };
 
-        if let Err(e) = notifications::delete_ping(&ctx.db, id, &url).await {
+        if let Err(e) = notifications::delete_ping(
+            &mut Context::make_db_client(&ctx.github.raw()).await?,
+            id,
+            notifications::Identifier::Url(&url),
+        )
+        .await
+        {
             log::warn!(
                 "failed to delete notification: url={}, user={:?}: {:?}",
                 url,

+ 16 - 2
src/zulip.rs

@@ -1,4 +1,4 @@
-use crate::db::notifications::{delete_ping, move_indices};
+use crate::db::notifications::{delete_ping, move_indices, Identifier};
 use crate::github::GithubClient;
 use crate::handlers::Context;
 use anyhow::Context as _;
@@ -107,7 +107,21 @@ async fn acknowledge(
         }
         None => anyhow::bail!("not enough words"),
     };
-    match delete_ping(&ctx.db, gh_id, url).await {
+    let ident = if let Ok(number) = url.parse::<usize>() {
+        Identifier::Index(
+            std::num::NonZeroUsize::new(number)
+                .ok_or_else(|| anyhow::anyhow!("index must be at least 1"))?,
+        )
+    } else {
+        Identifier::Url(url)
+    };
+    match delete_ping(
+        &mut Context::make_db_client(&ctx.github.raw()).await?,
+        gh_id,
+        ident,
+    )
+    .await
+    {
         Ok(()) => Ok(serde_json::to_string(&Response {
             content: &format!("Acknowledged {}.", url),
         })