fix: codex review — scope ack to user, deduplicate inbox entries
- ack_inbox now requires ?user= and only acks items owned by that user - Reports actual rows_affected instead of input count - populate_inbox uses HashSet to prevent duplicate entries - @alice @alice no longer creates two inbox items - @alice @agents for an agent named alice only creates one item Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@ impl ColonyClient {
|
|||||||
pub async fn ack_inbox(&self, ids: &[i64]) -> serde_json::Value {
|
pub async fn ack_inbox(&self, ids: &[i64]) -> serde_json::Value {
|
||||||
let body = AckRequest { ids: ids.to_vec() };
|
let body = AckRequest { ids: ids.to_vec() };
|
||||||
let res = self.http
|
let res = self.http
|
||||||
.post(self.url("/api/inbox/ack"))
|
.post(self.url(&format!("/api/inbox/ack?{}", self.config.user_query())))
|
||||||
.json(&body)
|
.json(&body)
|
||||||
.send().await.unwrap_or_else(|e| { eprintln!("colony unreachable: {e}"); process::exit(1); });
|
.send().await.unwrap_or_else(|e| { eprintln!("colony unreachable: {e}"); process::exit(1); });
|
||||||
if !res.status().is_success() { self.handle_error(res).await; }
|
if !res.status().is_success() { self.handle_error(res).await; }
|
||||||
|
|||||||
@@ -392,31 +392,37 @@ pub async fn get_inbox(
|
|||||||
|
|
||||||
pub async fn ack_inbox(
|
pub async fn ack_inbox(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
Query(user_param): Query<UserParam>,
|
||||||
Json(body): Json<AckRequest>,
|
Json(body): Json<AckRequest>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
|
let user_id = resolve_user(&state.db, &user_param).await?;
|
||||||
|
let mut acked = 0i64;
|
||||||
for id in &body.ids {
|
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(id)
|
||||||
|
.bind(&user_id)
|
||||||
.execute(&state.db)
|
.execute(&state.db)
|
||||||
.await?;
|
.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
|
/// 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) {
|
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 mentions = crate::db::parse_mentions(content);
|
||||||
|
let mut notified: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||||
|
|
||||||
for mention in &mentions {
|
for mention in &mentions {
|
||||||
// Resolve mentioned user
|
// 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)
|
.bind(mention)
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.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')")
|
let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'mention')")
|
||||||
.bind(&user_id)
|
.bind(&uid)
|
||||||
.bind(message_id)
|
.bind(message_id)
|
||||||
.bind(channel_id)
|
.bind(channel_id)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
@@ -432,12 +438,14 @@ async fn populate_inbox(db: &SqlitePool, message_id: &str, channel_id: &str, con
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
for agent_id in agents {
|
for agent_id in agents {
|
||||||
let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')")
|
if notified.insert(agent_id.clone()) {
|
||||||
.bind(&agent_id)
|
let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')")
|
||||||
.bind(message_id)
|
.bind(&agent_id)
|
||||||
.bind(channel_id)
|
.bind(message_id)
|
||||||
.execute(db)
|
.bind(channel_id)
|
||||||
.await;
|
.execute(db)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,12 +457,14 @@ async fn populate_inbox(db: &SqlitePool, message_id: &str, channel_id: &str, con
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
for ape_id in apes {
|
for ape_id in apes {
|
||||||
let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')")
|
if notified.insert(ape_id.clone()) {
|
||||||
.bind(&ape_id)
|
let _ = sqlx::query("INSERT INTO inbox (user_id, message_id, channel_id, trigger) VALUES (?, ?, ?, 'broadcast')")
|
||||||
.bind(message_id)
|
.bind(&ape_id)
|
||||||
.bind(channel_id)
|
.bind(message_id)
|
||||||
.execute(db)
|
.bind(channel_id)
|
||||||
.await;
|
.execute(db)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user