mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
refactor(db): move user and DM store ownership
Signed-off-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
committed by
Luke Tornquist
parent
b2dc379dfb
commit
2120889a0b
@@ -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)]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user