refactor(db): move user and DM store ownership

Signed-off-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
OpenAI Codex
2026-08-26 14:53:45 +00:00
committed by Luke Tornquist
parent b2dc379dfb
commit 2120889a0b
3 changed files with 199 additions and 189 deletions

View File

@@ -10,7 +10,9 @@ use uuid::Uuid;
use crate::channel::ChannelRecord;
use crate::error::{DbError, Result};
use crate::Db;
use buzz_core::CommunityId;
use buzz_datastore_tracing::datastore_span;
// -- Public structs -----------------------------------------------------------
@@ -514,6 +516,89 @@ fn row_to_channel_record(row: sqlx::postgres::PgRow) -> Result<ChannelRecord> {
})
}
// -- Db API -------------------------------------------------------------------
impl Db {
/// Find an existing DM by its participant hash.
#[datastore_span(name = "find_dm_by_participants", system = "postgresql")]
pub async fn find_dm_by_participants(
&self,
community_id: CommunityId,
participant_hash: &[u8],
) -> Result<Option<ChannelRecord>> {
crate::dm::find_dm_by_participants(&self.pool, community_id, participant_hash).await
}
/// Create or return an existing DM channel.
#[datastore_span(name = "create_dm", system = "postgresql")]
pub async fn create_dm(
&self,
community_id: CommunityId,
participants: &[&[u8]],
created_by: &[u8],
) -> Result<ChannelRecord> {
crate::dm::create_dm(&self.pool, community_id, participants, created_by).await
}
/// List all DMs for a user.
#[datastore_span(name = "list_dms_for_user", system = "postgresql")]
pub async fn list_dms_for_user(
&self,
community_id: CommunityId,
pubkey: &[u8],
limit: u32,
cursor: Option<Uuid>,
) -> Result<Vec<DmRecord>> {
crate::dm::list_dms_for_user(&self.pool, community_id, pubkey, limit, cursor).await
}
/// Open or retrieve a DM for the given participants.
#[datastore_span(name = "open_dm", system = "postgresql")]
pub async fn open_dm(
&self,
community_id: CommunityId,
pubkeys: &[&[u8]],
created_by: &[u8],
) -> Result<(ChannelRecord, bool)> {
crate::dm::open_dm(&self.pool, community_id, pubkeys, created_by).await
}
/// Hide a DM channel for a specific user.
///
/// The DM is not deleted — it can be restored by opening a new DM with
/// the same participants.
#[datastore_span(name = "hide_dm", system = "postgresql")]
pub async fn hide_dm(
&self,
community_id: CommunityId,
channel_id: Uuid,
pubkey: &[u8],
) -> Result<()> {
crate::dm::hide_dm(&self.pool, community_id, channel_id, pubkey).await
}
/// Unhide a DM channel for a specific user.
#[datastore_span(name = "unhide_dm", system = "postgresql")]
pub async fn unhide_dm(
&self,
community_id: CommunityId,
channel_id: Uuid,
pubkey: &[u8],
) -> Result<()> {
crate::dm::unhide_dm(&self.pool, community_id, channel_id, pubkey).await
}
/// List the channel IDs of all DMs the given user currently has hidden.
#[datastore_span(name = "list_hidden_dms", system = "postgresql")]
pub async fn list_hidden_dms(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Vec<Uuid>> {
crate::dm::list_hidden_dms(&self.pool, community_id, pubkey).await
}
}
// -- Tests --------------------------------------------------------------------
#[cfg(test)]

View File

@@ -1391,194 +1391,6 @@ impl Db {
.await
}
/// Ensure a user record exists (upsert).
///
/// Returns `true` if a new row was inserted (first time), `false` if it
/// already existed. Callers use the `true` return to increment
/// `buzz_users_created_total`.
#[datastore_span(name = "ensure_user", system = "postgresql")]
pub async fn ensure_user(&self, community_id: CommunityId, pubkey: &[u8]) -> Result<bool> {
user::ensure_user(&self.pool, community_id, pubkey).await
}
/// Get a single user record by pubkey.
#[datastore_span(name = "get_user", system = "postgresql")]
pub async fn get_user(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Option<user::UserProfile>> {
user::get_user(&self.pool, community_id, pubkey).await
}
/// Update a user's profile fields.
#[datastore_span(name = "update_user_profile", system = "postgresql")]
pub async fn update_user_profile(
&self,
community_id: CommunityId,
pubkey: &[u8],
display_name: Option<&str>,
avatar_url: Option<&str>,
about: Option<&str>,
nip05_handle: Option<&str>,
) -> Result<()> {
user::update_user_profile(
&self.pool,
community_id,
pubkey,
display_name,
avatar_url,
about,
nip05_handle,
)
.await
}
/// Look up a user by NIP-05 handle.
#[datastore_span(name = "get_user_by_nip05", system = "postgresql")]
pub async fn get_user_by_nip05(
&self,
community_id: CommunityId,
local_part: &str,
domain: &str,
) -> Result<Option<user::UserProfile>> {
user::get_user_by_nip05(&self.pool, community_id, local_part, domain).await
}
/// Search users by display name, NIP-05 handle, or pubkey prefix.
#[datastore_span(name = "search_users", system = "postgresql")]
pub async fn search_users(
&self,
community_id: CommunityId,
query: &str,
limit: u32,
) -> Result<Vec<user::UserSearchProfile>> {
user::search_users(&self.pool, community_id, query, limit).await
}
/// Atomically set agent owner — only if no owner is currently assigned.
/// Returns Ok(true) if set, Ok(false) if an owner already exists.
#[datastore_span(name = "set_agent_owner", system = "postgresql")]
pub async fn set_agent_owner(
&self,
community_id: CommunityId,
agent_pubkey: &[u8],
owner_pubkey: &[u8],
) -> Result<bool> {
user::set_agent_owner(&self.pool, community_id, agent_pubkey, owner_pubkey).await
}
/// Get the channel_add_policy and agent_owner_pubkey for a user.
#[datastore_span(name = "get_agent_channel_policy", system = "postgresql")]
pub async fn get_agent_channel_policy(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Option<(String, Option<Vec<u8>>)>> {
user::get_agent_channel_policy(&self.pool, community_id, pubkey).await
}
/// Check whether `actor_pubkey` is the agent owner of `target_pubkey`.
#[datastore_span(name = "is_agent_owner", system = "postgresql")]
pub async fn is_agent_owner(
&self,
community_id: CommunityId,
target_pubkey: &[u8],
actor_pubkey: &[u8],
) -> Result<bool> {
user::is_agent_owner(&self.pool, community_id, target_pubkey, actor_pubkey).await
}
/// Set the channel_add_policy for a user.
#[datastore_span(name = "set_channel_add_policy", system = "postgresql")]
pub async fn set_channel_add_policy(
&self,
community_id: CommunityId,
pubkey: &[u8],
policy: &str,
) -> Result<()> {
user::set_channel_add_policy(&self.pool, community_id, pubkey, policy).await
}
/// Find an existing DM by its participant hash.
#[datastore_span(name = "find_dm_by_participants", system = "postgresql")]
pub async fn find_dm_by_participants(
&self,
community_id: CommunityId,
participant_hash: &[u8],
) -> Result<Option<channel::ChannelRecord>> {
dm::find_dm_by_participants(&self.pool, community_id, participant_hash).await
}
/// Create or return an existing DM channel.
#[datastore_span(name = "create_dm", system = "postgresql")]
pub async fn create_dm(
&self,
community_id: CommunityId,
participants: &[&[u8]],
created_by: &[u8],
) -> Result<channel::ChannelRecord> {
dm::create_dm(&self.pool, community_id, participants, created_by).await
}
/// List all DMs for a user.
#[datastore_span(name = "list_dms_for_user", system = "postgresql")]
pub async fn list_dms_for_user(
&self,
community_id: CommunityId,
pubkey: &[u8],
limit: u32,
cursor: Option<Uuid>,
) -> Result<Vec<dm::DmRecord>> {
dm::list_dms_for_user(&self.pool, community_id, pubkey, limit, cursor).await
}
/// Open or retrieve a DM for the given participants.
#[datastore_span(name = "open_dm", system = "postgresql")]
pub async fn open_dm(
&self,
community_id: CommunityId,
pubkeys: &[&[u8]],
created_by: &[u8],
) -> Result<(channel::ChannelRecord, bool)> {
dm::open_dm(&self.pool, community_id, pubkeys, created_by).await
}
/// Hide a DM channel for a specific user.
///
/// The DM is not deleted — it can be restored by opening a new DM with
/// the same participants.
#[datastore_span(name = "hide_dm", system = "postgresql")]
pub async fn hide_dm(
&self,
community_id: CommunityId,
channel_id: Uuid,
pubkey: &[u8],
) -> Result<()> {
dm::hide_dm(&self.pool, community_id, channel_id, pubkey).await
}
/// Unhide a DM channel for a specific user.
#[datastore_span(name = "unhide_dm", system = "postgresql")]
pub async fn unhide_dm(
&self,
community_id: CommunityId,
channel_id: Uuid,
pubkey: &[u8],
) -> Result<()> {
dm::unhide_dm(&self.pool, community_id, channel_id, pubkey).await
}
/// List the channel IDs of all DMs the given user currently has hidden.
#[datastore_span(name = "list_hidden_dms", system = "postgresql")]
pub async fn list_hidden_dms(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Vec<Uuid>> {
dm::list_hidden_dms(&self.pool, community_id, pubkey).await
}
/// Shared route decision for one read: evaluate the predicate against a
/// proved reader session and record the decision. Fail closed to the
/// writer everywhere.

View File

@@ -1,7 +1,9 @@
//! User CRUD operations.
use crate::error::Result;
use crate::Db;
use buzz_core::CommunityId;
use buzz_datastore_tracing::datastore_span;
use sqlx::PgPool;
use sqlx::Row;
@@ -398,13 +400,124 @@ pub async fn set_channel_add_policy(
Ok(())
}
impl Db {
/// Ensure a user record exists (upsert).
///
/// Returns `true` if a new row was inserted (first time), `false` if it
/// already existed. Callers use the `true` return to increment
/// `buzz_users_created_total`.
#[datastore_span(name = "ensure_user", system = "postgresql")]
pub async fn ensure_user(&self, community_id: CommunityId, pubkey: &[u8]) -> Result<bool> {
crate::user::ensure_user(&self.pool, community_id, pubkey).await
}
/// Get a single user record by pubkey.
#[datastore_span(name = "get_user", system = "postgresql")]
pub async fn get_user(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Option<UserProfile>> {
crate::user::get_user(&self.pool, community_id, pubkey).await
}
/// Update a user's profile fields.
#[datastore_span(name = "update_user_profile", system = "postgresql")]
pub async fn update_user_profile(
&self,
community_id: CommunityId,
pubkey: &[u8],
display_name: Option<&str>,
avatar_url: Option<&str>,
about: Option<&str>,
nip05_handle: Option<&str>,
) -> Result<()> {
crate::user::update_user_profile(
&self.pool,
community_id,
pubkey,
display_name,
avatar_url,
about,
nip05_handle,
)
.await
}
/// Look up a user by NIP-05 handle.
#[datastore_span(name = "get_user_by_nip05", system = "postgresql")]
pub async fn get_user_by_nip05(
&self,
community_id: CommunityId,
local_part: &str,
domain: &str,
) -> Result<Option<UserProfile>> {
crate::user::get_user_by_nip05(&self.pool, community_id, local_part, domain).await
}
/// Search users by display name, NIP-05 handle, or pubkey prefix.
#[datastore_span(name = "search_users", system = "postgresql")]
pub async fn search_users(
&self,
community_id: CommunityId,
query: &str,
limit: u32,
) -> Result<Vec<UserSearchProfile>> {
crate::user::search_users(&self.pool, community_id, query, limit).await
}
/// Atomically set agent owner — only if no owner is currently assigned.
/// Returns Ok(true) if set, Ok(false) if an owner already exists.
#[datastore_span(name = "set_agent_owner", system = "postgresql")]
pub async fn set_agent_owner(
&self,
community_id: CommunityId,
agent_pubkey: &[u8],
owner_pubkey: &[u8],
) -> Result<bool> {
crate::user::set_agent_owner(&self.pool, community_id, agent_pubkey, owner_pubkey).await
}
/// Get the channel_add_policy and agent_owner_pubkey for a user.
#[datastore_span(name = "get_agent_channel_policy", system = "postgresql")]
pub async fn get_agent_channel_policy(
&self,
community_id: CommunityId,
pubkey: &[u8],
) -> Result<Option<(String, Option<Vec<u8>>)>> {
crate::user::get_agent_channel_policy(&self.pool, community_id, pubkey).await
}
/// Check whether `actor_pubkey` is the agent owner of `target_pubkey`.
#[datastore_span(name = "is_agent_owner", system = "postgresql")]
pub async fn is_agent_owner(
&self,
community_id: CommunityId,
target_pubkey: &[u8],
actor_pubkey: &[u8],
) -> Result<bool> {
crate::user::is_agent_owner(&self.pool, community_id, target_pubkey, actor_pubkey).await
}
/// Set the channel_add_policy for a user.
#[datastore_span(name = "set_channel_add_policy", system = "postgresql")]
pub async fn set_channel_add_policy(
&self,
community_id: CommunityId,
pubkey: &[u8],
policy: &str,
) -> Result<()> {
crate::user::set_channel_add_policy(&self.pool, community_id, pubkey, policy).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Db;
use nostr::Keys;
const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz";
const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; // sadscan:disable np.postgres.1 -- local test-only credentials
async fn setup_db() -> Db {
let pool = PgPool::connect(TEST_DB_URL)