use uuid::Uuid;
use crate::application::service::chatter_acl::MessagingIdentity;
use crate::domain::event::constants::{discuss_channel, stage_bus_event};
use crate::infrastructure::persistence::channel_member_repository::{
ChannelMemberRepository, MemberKey,
};
#[derive(Debug, thiserror::Error)]
pub enum MemberError {
#[error("db: {0}")]
Db(#[from] sqlx::Error),
#[error("invalid input: {0}")]
Invalid(String),
#[error("not a member of channel {0}")]
NotMember(Uuid),
}
pub struct ChannelMemberWriteService {
pool: sqlx::PgPool,
}
impl ChannelMemberWriteService {
pub fn new(pool: sqlx::PgPool) -> Self {
Self { pool }
}
pub async fn mark_as_read(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
message_id: Uuid,
) -> Result<bool, MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, true)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
let advanced =
ChannelMemberRepository::advance_seen(&mut tx, member_id, message_id).await?;
if advanced {
stage_bus_event(
&mut tx,
"MemberSeenAdvanced",
"DiscussChannelMember",
member_id,
discuss_channel(channel_id),
"discuss.channel.member/seen",
serde_json::json!({
"channel_id": channel_id,
"member_id": member_id,
"seen_message_id": message_id,
}),
)
.await?;
}
tx.commit().await?;
Ok(advanced)
}
pub async fn mark_fetched(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
message_id: Uuid,
) -> Result<bool, MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, true)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
let advanced =
ChannelMemberRepository::advance_fetched(&mut tx, member_id, message_id).await?;
tx.commit().await?;
Ok(advanced)
}
pub async fn set_new_message_separator(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
message_id: Uuid,
) -> Result<(), MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
ChannelMemberRepository::set_separator(&mut tx, member_id, message_id).await?;
tx.commit().await?;
Ok(())
}
pub async fn join(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
) -> Result<bool, MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let joined = ChannelMemberRepository::upsert_join(&mut tx, &key).await?;
if joined {
stage_bus_event(
&mut tx,
"MemberJoined",
"DiscussChannelMember",
channel_id,
discuss_channel(channel_id),
"discuss.channel/member_added",
serde_json::json!({
"channel_id": channel_id,
"partner_id": identity.partner_id(),
}),
)
.await?;
}
tx.commit().await?;
Ok(joined)
}
pub async fn leave(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
) -> Result<bool, MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
let left = ChannelMemberRepository::soft_delete_member(&mut tx, member_id).await?;
if left {
stage_bus_event(
&mut tx,
"MemberLeft",
"DiscussChannelMember",
member_id,
discuss_channel(channel_id),
"discuss.channel/member_removed",
serde_json::json!({
"channel_id": channel_id,
"partner_id": identity.partner_id(),
}),
)
.await?;
}
tx.commit().await?;
Ok(left)
}
pub async fn set_mute(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
until: Option<chrono::DateTime<chrono::Utc>>,
forever: bool,
) -> Result<(), MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
ChannelMemberRepository::set_mute(&mut tx, member_id, until, forever).await?;
tx.commit().await?;
Ok(())
}
pub async fn set_custom_notifications(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
value: Option<&str>,
) -> Result<(), MemberError> {
if let Some(v) = value {
if !matches!(v, "all" | "mentions" | "no_notif") {
return Err(MemberError::Invalid(format!(
"custom_notifications must be all|mentions|no_notif, got {v:?}"
)));
}
}
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
ChannelMemberRepository::set_custom_notifications(&mut tx, member_id, value).await?;
tx.commit().await?;
Ok(())
}
pub async fn set_pinned(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
pinned: bool,
) -> Result<(), MemberError> {
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
ChannelMemberRepository::set_pinned(&mut tx, member_id, pinned).await?;
tx.commit().await?;
Ok(())
}
pub async fn set_fold_state(
&self,
channel_id: Uuid,
identity: &MessagingIdentity,
fold: Option<&str>,
) -> Result<(), MemberError> {
if let Some(f) = fold {
if !matches!(f, "open" | "closed") {
return Err(MemberError::Invalid(format!(
"fold_state must be open|closed, got {f:?}"
)));
}
}
let mut tx = self.pool.begin().await?;
let key = MemberKey { channel_id, identity };
let member_id = ChannelMemberRepository::member_id(&mut tx, &key, false)
.await?
.ok_or(MemberError::NotMember(channel_id))?;
ChannelMemberRepository::set_fold_state(&mut tx, member_id, fold).await?;
tx.commit().await?;
Ok(())
}
}