Преглед изворни кода

Implement adding notifications through Zulip

Mark Rousskov пре 5 година
родитељ
комит
abb62fd8af
3 измењених фајлова са 72 додато и 5 уклоњено
  1. 5 3
      src/db/notifications.rs
  2. 13 1
      src/handlers/notification.rs
  3. 54 1
      src/zulip.rs

+ 5 - 3
src/db/notifications.rs

@@ -4,7 +4,6 @@ use tokio_postgres::Client as DbClient;
 
 pub struct Notification {
     pub user_id: i64,
-    pub username: String,
     pub origin_url: String,
     pub origin_html: String,
     pub short_description: Option<String>,
@@ -15,14 +14,17 @@ pub struct Notification {
     pub team_name: Option<String>,
 }
 
-pub async fn record_ping(db: &DbClient, notification: &Notification) -> anyhow::Result<()> {
+pub async fn record_username(db: &DbClient, user_id: i64, username: String) -> anyhow::Result<()> {
     db.execute(
         "INSERT INTO users (user_id, username) VALUES ($1, $2) ON CONFLICT DO NOTHING",
-        &[&notification.user_id, &notification.username],
+        &[&user_id, &username],
     )
     .await
     .context("inserting user id / username")?;
+    Ok(())
+}
 
+pub async fn record_ping(db: &DbClient, notification: &Notification) -> anyhow::Result<()> {
     db.execute("INSERT INTO notifications (user_id, origin_url, origin_html, time, short_description, team_name) VALUES ($1, $2, $3, $4, $5, $6)",
         &[&notification.user_id, &notification.origin_url, &notification.origin_html, &notification.time, &notification.short_description, &notification.team_name],
         ).await.context("inserting notification")?;

+ 13 - 1
src/handlers/notification.rs

@@ -91,6 +91,7 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
         .captures_iter(body)
         .map(|c| c.get(1).unwrap().as_str().to_owned())
         .collect::<HashSet<_>>();
+    let mut users_notified = HashSet::new();
     log::trace!("Captured usernames in comment: {:?}", caps);
     for login in caps {
         let (users, team_name) = if login.contains('/') {
@@ -174,11 +175,22 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
         };
 
         for user in users {
+            if !users_notified.insert(user.id.unwrap()) {
+                // Skip users already associated with this event.
+                continue;
+            }
+
+            if let Err(err) = notifications::record_username(&ctx.db, user.id.unwrap(), user.login)
+                .await
+                .context("failed to record username")
+            {
+                log::error!("record username: {:?}", err);
+            }
+
             if let Err(err) = notifications::record_ping(
                 &ctx.db,
                 &notifications::Notification {
                     user_id: user.id.unwrap(),
-                    username: user.login,
                     origin_url: event.html_url().unwrap().to_owned(),
                     origin_html: body.to_owned(),
                     time: event.time(),

+ 54 - 1
src/zulip.rs

@@ -1,4 +1,4 @@
-use crate::db::notifications::{delete_ping, move_indices, Identifier};
+use crate::db::notifications::{self, delete_ping, move_indices, record_ping, Identifier};
 use crate::github::GithubClient;
 use crate::handlers::Context;
 use anyhow::Context as _;
@@ -76,6 +76,16 @@ pub async fn respond(ctx: &Context, req: Request) -> String {
             })
             .unwrap(),
         },
+        Some("add") => match add_notification(&ctx, gh_id, words).await {
+            Ok(r) => r,
+            Err(e) => serde_json::to_string(&Response {
+                content: &format!(
+                    "Failed to parse movement, expected `add <url> <description (multiple words)>`: {:?}.",
+                    e
+                ),
+            })
+            .unwrap(),
+        },
         Some("move") => match move_notification(&ctx, gh_id, words).await {
             Ok(r) => r,
             Err(e) => serde_json::to_string(&Response {
@@ -133,6 +143,49 @@ async fn acknowledge(
     }
 }
 
+async fn add_notification(
+    ctx: &Context,
+    gh_id: i64,
+    mut words: impl Iterator<Item = &str>,
+) -> anyhow::Result<String> {
+    let url = match words.next() {
+        Some(idx) => idx,
+        None => anyhow::bail!("url not present"),
+    };
+    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 record_ping(
+        &ctx.db,
+        &notifications::Notification {
+            user_id: gh_id,
+            origin_url: url.to_owned(),
+            origin_html: String::new(),
+            short_description: description,
+            time: chrono::Utc::now().into(),
+            team_name: None,
+        },
+    )
+    .await
+    {
+        Ok(()) => Ok(serde_json::to_string(&Response {
+            content: "Created!",
+        })
+        .unwrap()),
+        Err(e) => Ok(serde_json::to_string(&Response {
+            content: &format!("Failed to create: {:?}", e),
+        })
+        .unwrap()),
+    }
+}
 async fn move_notification(
     ctx: &Context,
     gh_id: i64,