Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 52 additions & 44 deletions desktop/src-tauri/src/archive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,21 +483,23 @@ pub async fn list_save_subscriptions(
/// Does NOT purge already-archived event data — retention is decoupled in v1.
/// GC of orphaned event rows happens in P4 purge commands, not here.
#[tauri::command]
pub fn delete_save_subscription(
pub async fn delete_save_subscription(
state: State<'_, AppState>,
scope_type: ScopeType,
scope_value: String,
) -> Result<bool, String> {
let identity_pk = identity_pubkey(&state)?;
let relay_url = relay_ws_url_with_override(&state);
let conn = open_db()?;
store::delete_save_subscription(
&conn,
&identity_pk,
&relay_url,
scope_type.as_str(),
&scope_value,
)
run_archive_db_task(move |conn| {
store::delete_save_subscription(
conn,
&identity_pk,
&relay_url,
scope_type.as_str(),
&scope_value,
)
})
.await
}

// ── read_archived_events ─────────────────────────────────────────────────────
Expand All @@ -516,7 +518,7 @@ pub fn delete_save_subscription(
/// newest-first order. Compound cursor `(before_created_at, before_id)` works
/// identically to `read_archived_events`.
#[tauri::command]
pub fn read_archived_observer_events_for_channel(
pub async fn read_archived_observer_events_for_channel(
state: State<'_, AppState>,
channel_id: String,
before_created_at: Option<i64>,
Expand All @@ -525,16 +527,18 @@ pub fn read_archived_observer_events_for_channel(
) -> Result<Vec<String>, String> {
let identity_pk = identity_pubkey(&state)?;
let relay_url = relay_ws_url_with_override(&state);
let conn = open_db()?;
store::read_archived_observer_events_for_channel(
&conn,
&identity_pk,
&relay_url,
&channel_id,
before_created_at,
before_id.as_deref(),
limit.unwrap_or(DEFAULT_READ_LIMIT),
)
run_archive_db_task(move |conn| {
store::read_archived_observer_events_for_channel(
conn,
&identity_pk,
&relay_url,
&channel_id,
before_created_at,
before_id.as_deref(),
limit.unwrap_or(DEFAULT_READ_LIMIT),
)
})
.await
}

// ── index_observer_channel_id ─────────────────────────────────────────────────
Expand All @@ -548,24 +552,26 @@ pub fn read_archived_observer_events_for_channel(
///
/// Idempotent: rows that are already indexed are left unchanged.
#[tauri::command]
pub fn index_observer_channel_id(
pub async fn index_observer_channel_id(
state: State<'_, AppState>,
entries: Vec<ObserverChannelIndexEntry>,
) -> Result<(), String> {
let identity_pk = identity_pubkey(&state)?;
let relay_url = relay_ws_url_with_override(&state);
let conn = open_db()?;
for entry in &entries {
store::upsert_observer_channel_index(
&conn,
&identity_pk,
&relay_url,
&entry.event_id,
entry.channel_id.as_deref(),
entry.created_at,
)?;
}
Ok(())
run_archive_db_task(move |conn| {
for entry in &entries {
store::upsert_observer_channel_index(
conn,
&identity_pk,
&relay_url,
&entry.event_id,
entry.channel_id.as_deref(),
entry.created_at,
)?;
}
Ok(())
})
.await
}

/// A single (event_id, channel_id?, created_at) record used by
Expand All @@ -591,21 +597,23 @@ pub struct ObserverChannelIndexEntry {
/// Together these constitute the one-shot idempotent backfill required by the
/// Slice 1 acceptance criteria (Thufir Pass 4).
#[tauri::command]
pub fn read_unindexed_observer_rows(
pub async fn read_unindexed_observer_rows(
state: State<'_, AppState>,
) -> Result<Vec<RawObserverRow>, String> {
let identity_pk = identity_pubkey(&state)?;
let relay_url = relay_ws_url_with_override(&state);
let conn = open_db()?;
let rows = store::read_unindexed_observer_rows(&conn, &identity_pk, &relay_url)?;
Ok(rows
.into_iter()
.map(|(id, raw_json, created_at)| RawObserverRow {
id,
raw_json,
created_at,
})
.collect())
run_archive_db_task(move |conn| {
let rows = store::read_unindexed_observer_rows(conn, &identity_pk, &relay_url)?;
Ok(rows
.into_iter()
.map(|(id, raw_json, created_at)| RawObserverRow {
id,
raw_json,
created_at,
})
.collect())
})
.await
}

/// Wire type returned by `read_unindexed_observer_rows`.
Expand Down
28 changes: 17 additions & 11 deletions desktop/src-tauri/src/commands/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,29 @@ pub async fn sign_event(
}

#[tauri::command]
pub fn decrypt_observer_event(
pub async fn decrypt_observer_event(
event_json: String,
state: State<'_, AppState>,
) -> Result<serde_json::Value, String> {
let keys = state.signing_keys()?;
let event = Event::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?;

// Defense-in-depth: verify event ID and signature before decrypting.
if !event.verify_id() {
return Err("observer event has invalid ID".into());
}
if !event.verify_signature() {
return Err("observer event has invalid signature".into());
}
tauri::async_runtime::spawn_blocking(move || {
let event =
Event::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?;

buzz_core_pkg::observer::decrypt_observer_payload(&keys, &event)
.map_err(|error| format!("decrypt observer event failed: {error}"))
// Defense-in-depth: verify event ID and signature before decrypting.
if !event.verify_id() {
return Err("observer event has invalid ID".into());
}
if !event.verify_signature() {
return Err("observer event has invalid signature".into());
}

buzz_core_pkg::observer::decrypt_observer_payload(&keys, &event)
.map_err(|error| format!("decrypt observer event failed: {error}"))
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
}

#[tauri::command]
Expand Down
Loading