From 12df5fb791bacbd63a89a287fe972434e6ef1003 Mon Sep 17 00:00:00 2001 From: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:59:33 -0400 Subject: [PATCH] fix(db): add tenant-safe query defaults and reaper host provenance Add EventQuery::for_community so relay call sites can keep concise struct updates without restoring a tenantless Default. The constructor requires the server-resolved CommunityId and preserves the old optional filter defaults everywhere else. Return the owning community host from the ephemeral-channel reaper by joining communities in the archive UPDATE. Reaper consumers can now build TenantContext per archived row from DB-resolved community+host instead of hoisting or forging a batch-level tenant. Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> --- crates/buzz-db/src/channel.rs | 73 +++++++++++++++++++++++++++++++---- crates/buzz-db/src/event.rs | 30 ++++++++++++++ 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/crates/buzz-db/src/channel.rs b/crates/buzz-db/src/channel.rs index bdbe812d4..8ffc5866f 100644 --- a/crates/buzz-db/src/channel.rs +++ b/crates/buzz-db/src/channel.rs @@ -733,10 +733,12 @@ pub struct BotChannelEntry { } /// A channel archived by the ephemeral-channel reaper. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ReapedEphemeralChannel { /// Community that owns the archived channel. pub community_id: CommunityId, + /// Normalized host mapped to that community. + pub host: String, /// Archived channel UUID. pub channel_id: Uuid, } @@ -1325,17 +1327,19 @@ pub async fn bump_ttl_deadline( /// Archive ephemeral channels whose TTL deadline has passed. /// -/// Returns the `(community_id, channel_id)` list that was archived. Idempotent — the +/// Returns the `(community_id, host, channel_id)` list that was archived. Idempotent — the /// `archived_at IS NULL` guard prevents double-archiving even if called /// concurrently from multiple relay pods. pub async fn reap_expired_ephemeral_channels(pool: &PgPool) -> Result> { let rows = sqlx::query( - "UPDATE channels SET archived_at = NOW() \ - WHERE ttl_seconds IS NOT NULL \ - AND ttl_deadline < NOW() \ - AND archived_at IS NULL \ - AND deleted_at IS NULL \ - RETURNING community_id, id", + "UPDATE channels AS ch SET archived_at = NOW() \ + FROM communities AS c \ + WHERE ch.community_id = c.id \ + AND ch.ttl_seconds IS NOT NULL \ + AND ch.ttl_deadline < NOW() \ + AND ch.archived_at IS NULL \ + AND ch.deleted_at IS NULL \ + RETURNING ch.community_id, c.host, ch.id", ) .fetch_all(pool) .await?; @@ -1343,9 +1347,11 @@ pub async fn reap_expired_ephemeral_channels(pool: &PgPool) -> Result, } +impl EventQuery { + /// Construct an unconstrained query inside a server-resolved community. + /// + /// `community_id` has no safe default. This keeps call sites concise while + /// making tenant provenance explicit at construction. + #[must_use] + pub const fn for_community(community_id: CommunityId) -> Self { + Self { + community_id, + channel_id: None, + kinds: None, + pubkey: None, + since: None, + until: None, + limit: None, + offset: None, + p_tag_hex: None, + d_tag: None, + d_tags: None, + before_id: None, + global_only: false, + authors: None, + ids: None, + e_tags: None, + channel_ids: None, + max_limit: None, + } + } +} + /// Maximum length for a `d_tag` value (bytes). NIP-33 d-tags are short identifiers; /// anything beyond this is either a bug or abuse. pub const D_TAG_MAX_LEN: usize = 1024;