use std::collections::BTreeSet;
use crate::GroupId;
use nostr::{PublicKey, RelayUrl, Timestamp};
pub mod error;
pub mod types;
pub mod validation;
use self::error::GroupError;
use self::types::*;
use crate::messages::types::Message;
pub const DEFAULT_MESSAGE_LIMIT: usize = 1000;
pub const MAX_MESSAGE_LIMIT: usize = 10000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MessageSortOrder {
#[default]
CreatedAtFirst,
ProcessedAtFirst,
}
bounded_pagination! {
default_limit: DEFAULT_MESSAGE_LIMIT,
max_limit: MAX_MESSAGE_LIMIT,
error_type: GroupError,
validate_fn: validate_message_limit,
extra {
sort_order: MessageSortOrder,
}
}
impl Pagination {
pub fn with_sort_order(
limit: Option<usize>,
offset: Option<usize>,
sort_order: MessageSortOrder,
) -> Self {
Self {
limit,
offset,
sort_order: Some(sort_order),
}
}
pub fn sort_order(&self) -> MessageSortOrder {
self.sort_order.unwrap_or_default()
}
}
#[macro_export]
macro_rules! impl_exporter_secret_methods {
($backend_get:ident, $backend_save:ident) => {
fn get_group_exporter_secret(
&self,
group_id: &$crate::GroupId,
epoch: u64,
) -> Result<
Option<$crate::groups::types::GroupExporterSecret>,
$crate::groups::error::GroupError,
> {
$backend_get!(
self,
group_id,
epoch,
"group-event",
group_exporter_secrets_cache
)
}
fn save_group_exporter_secret(
&self,
group_exporter_secret: $crate::groups::types::GroupExporterSecret,
) -> Result<(), $crate::groups::error::GroupError> {
$backend_save!(
self,
group_exporter_secret,
"group-event",
group_exporter_secrets_cache
)
}
fn get_group_legacy_exporter_secret(
&self,
group_id: &$crate::GroupId,
epoch: u64,
) -> Result<
Option<$crate::groups::types::GroupExporterSecret>,
$crate::groups::error::GroupError,
> {
$backend_get!(
self,
group_id,
epoch,
"legacy-group-event",
group_legacy_exporter_secrets_cache
)
}
fn save_group_legacy_exporter_secret(
&self,
group_exporter_secret: $crate::groups::types::GroupExporterSecret,
) -> Result<(), $crate::groups::error::GroupError> {
$backend_save!(
self,
group_exporter_secret,
"legacy-group-event",
group_legacy_exporter_secrets_cache
)
}
fn get_group_mip04_exporter_secret(
&self,
group_id: &$crate::GroupId,
epoch: u64,
) -> Result<
Option<$crate::groups::types::GroupExporterSecret>,
$crate::groups::error::GroupError,
> {
$backend_get!(
self,
group_id,
epoch,
"encrypted-media",
group_mip04_exporter_secrets_cache
)
}
fn save_group_mip04_exporter_secret(
&self,
group_exporter_secret: $crate::groups::types::GroupExporterSecret,
) -> Result<(), $crate::groups::error::GroupError> {
$backend_save!(
self,
group_exporter_secret,
"encrypted-media",
group_mip04_exporter_secrets_cache
)
}
};
}
#[inline]
pub fn group_not_found() -> GroupError {
GroupError::InvalidParameters("Group not found".to_string())
}
pub trait GroupStorage {
fn all_groups(&self) -> Result<Vec<Group>, GroupError>;
fn find_group_by_mls_group_id(&self, group_id: &GroupId) -> Result<Option<Group>, GroupError>;
fn find_group_by_nostr_group_id(
&self,
nostr_group_id: &[u8; 32],
) -> Result<Option<Group>, GroupError>;
fn save_group(&self, group: Group) -> Result<(), GroupError>;
fn messages(
&self,
group_id: &GroupId,
pagination: Option<Pagination>,
) -> Result<Vec<Message>, GroupError>;
fn last_message(
&self,
group_id: &GroupId,
sort_order: MessageSortOrder,
) -> Result<Option<Message>, GroupError>;
fn admins(&self, group_id: &GroupId) -> Result<BTreeSet<PublicKey>, GroupError>;
fn group_relays(&self, group_id: &GroupId) -> Result<BTreeSet<GroupRelay>, GroupError>;
fn replace_group_relays(
&self,
group_id: &GroupId,
relays: BTreeSet<RelayUrl>,
) -> Result<(), GroupError>;
fn get_group_exporter_secret(
&self,
group_id: &GroupId,
epoch: u64,
) -> Result<Option<GroupExporterSecret>, GroupError>;
fn save_group_exporter_secret(
&self,
group_exporter_secret: GroupExporterSecret,
) -> Result<(), GroupError>;
fn get_group_legacy_exporter_secret(
&self,
group_id: &GroupId,
epoch: u64,
) -> Result<Option<GroupExporterSecret>, GroupError>;
fn save_group_legacy_exporter_secret(
&self,
group_exporter_secret: GroupExporterSecret,
) -> Result<(), GroupError>;
fn get_group_mip04_exporter_secret(
&self,
group_id: &GroupId,
epoch: u64,
) -> Result<Option<GroupExporterSecret>, GroupError>;
fn save_group_mip04_exporter_secret(
&self,
group_exporter_secret: GroupExporterSecret,
) -> Result<(), GroupError>;
fn prune_group_exporter_secrets_before_epoch(
&self,
group_id: &GroupId,
min_epoch_to_keep: u64,
) -> Result<(), GroupError>;
fn groups_needing_self_update(&self, threshold_secs: u64) -> Result<Vec<GroupId>, GroupError> {
let now = Timestamp::now().as_secs();
let groups = self.all_groups()?;
Ok(groups
.into_iter()
.filter(|g| {
if g.state != types::GroupState::Active {
return false;
}
match g.self_update_state {
types::SelfUpdateState::Required => true,
types::SelfUpdateState::CompletedAt(ts) => {
now.saturating_sub(ts.as_secs()) >= threshold_secs
}
}
})
.map(|g| g.mls_group_id)
.collect())
}
}