diff --git a/crates/colony-cli/src/client.rs b/crates/colony-cli/src/client.rs index ab64800..5eaf173 100644 --- a/crates/colony-cli/src/client.rs +++ b/crates/colony-cli/src/client.rs @@ -90,7 +90,7 @@ impl ColonyClient { pub async fn ack_inbox(&self, ids: &[i64]) -> serde_json::Value { let body = AckRequest { ids: ids.to_vec() }; let res = self.http - .post(self.url("/api/inbox/ack")) + .post(self.url(&format!("/api/inbox/ack?{}", self.config.user_query()))) .json(&body) .send().await.unwrap_or_else(|e| { eprintln!("colony unreachable: {e}"); process::exit(1); }); if !res.status().is_success() { self.handle_error(res).await; } diff --git a/crates/colony/src/routes.rs b/crates/colony/src/routes.rs index 255cb77..422033d 100644 --- a/crates/colony/src/routes.rs +++ b/crates/colony/src/routes.rs @@ -392,31 +392,37 @@ pub async fn get_inbox( pub async fn ack_inbox( State(state): State, + Query(user_param): Query, Json(body): Json, ) -> Result { + let user_id = resolve_user(&state.db, &user_param).await?; + let mut acked = 0i64; for id in &body.ids { - sqlx::query("UPDATE inbox SET acked_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ? AND acked_at IS NULL") + let result = sqlx::query("UPDATE inbox SET acked_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ? AND user_id = ? AND acked_at IS NULL") .bind(id) + .bind(&user_id) .execute(&state.db) .await?; + acked += result.rows_affected() as i64; } - Ok(Json(serde_json::json!({"acked": body.ids.len()}))) + Ok(Json(serde_json::json!({"acked": acked}))) } /// Populate inbox entries when a message is posted async fn populate_inbox(db: &SqlitePool, message_id: &str, channel_id: &str, content: &str, sender_id: &str) { let mentions = crate::db::parse_mentions(content); + let mut notified: std::collections::HashSet = std::collections::HashSet::new(); for mention in &mentions { // Resolve mentioned user - if let Ok(Some(user_id)) = sqlx::query_scalar::<_, String>("SELECT id FROM users WHERE username = ?") + if let Ok(Some(uid)) = sqlx::query_scalar::<_, String>("SELECT id FROM users WHERE username = ?") .bind(mention) .fetch_optional(db) .await { - if user_id != sender_id { + if uid != sender_id && notified.insert(uid.clone()) { let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'mention')") - .bind(&user_id) + .bind(&uid) .bind(message_id) .bind(channel_id) .execute(db) @@ -432,12 +438,14 @@ async fn populate_inbox(db: &SqlitePool, message_id: &str, channel_id: &str, con .await .unwrap_or_default(); for agent_id in agents { - let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')") - .bind(&agent_id) - .bind(message_id) - .bind(channel_id) - .execute(db) - .await; + if notified.insert(agent_id.clone()) { + let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')") + .bind(&agent_id) + .bind(message_id) + .bind(channel_id) + .execute(db) + .await; + } } } @@ -449,12 +457,14 @@ async fn populate_inbox(db: &SqlitePool, message_id: &str, channel_id: &str, con .await .unwrap_or_default(); for ape_id in apes { - let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')") - .bind(&ape_id) - .bind(message_id) - .bind(channel_id) - .execute(db) - .await; + if notified.insert(ape_id.clone()) { + let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')") + .bind(&ape_id) + .bind(message_id) + .bind(channel_id) + .execute(db) + .await; + } } } }