signer-daemon 0.3.2

Signer daemon package.
Documentation
use std::collections::BTreeSet;

use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
use serde::{Deserialize, Serialize};

use crate::{
    SignerDaemon, SignerDaemonCore,
    entity::message,
    model::viewobject::{MessageContent, Null},
};

use super::MessageVO;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "poem-openapi", derive(poem_openapi::Union))]
pub enum ChatVO {
    Private(PrivateChatVO),
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "poem-openapi", derive(poem_openapi::Object))]
pub struct PrivateChatVO {
    pub peers: Vec<String>,
}

impl ChatVO {
    pub fn chat_variant(&self) -> String {
        match self {
            ChatVO::Private(_) => format!("Private"),
        }
    }

    pub async fn chat_key(&self, core: &SignerDaemonCore) -> crate::DaemonResult<String> {
        match self {
            ChatVO::Private(private) => {
                let self_key = &core.pub_key;
                // peers 为 1 时表示是用户向自己发起的私聊,peers 为 2 时表示是用户向他人发起的私聊
                if private.peers.len() == 0 || private.peers.len() > 2 {
                    return Err(crate::DaemonError::Signer(crate::SignerError::Msg(
                        "private chat peers length must less than 2 and greater than 0".to_string(),
                    )));
                }

                let peers = &private.peers;

                if peers.len() == 1 {
                    return Ok(peers[0].clone());
                }

                if *self_key == peers[0] {
                    Ok(peers[1].clone())
                } else {
                    Ok(peers[0].clone())
                }
            }
        }
    }

    pub async fn uncheck_message_count(&self, core: &SignerDaemonCore) -> crate::DaemonResult<u64> {
        let chat_variant = self.chat_variant();
        let chat_key = self.chat_key(core).await?;

        let user_key = core.pub_key.clone();

        let id_vec: Vec<String> = message::Entity::find()
            .select_only()
            .column(message::Column::Id)
            .filter(
                message::Column::ChatVariant
                    .eq(&chat_variant)
                    .and(message::Column::ChatKey.eq(&chat_key))
                    .and(message::Column::ReceiverKeys.contains(user_key.clone())),
            )
            .into_tuple()
            .all(&core.db)
            .await?;

        let checked = serde_json::to_string(&MessageContent::Check(Null {}))?;
        let checked_id_vec: Vec<String> = message::Entity::find()
            .select_only()
            .column(message::Column::ParentId)
            .filter(
                message::Column::ChatVariant
                    .eq(&chat_variant)
                    .and(message::Column::ChatKey.eq(&chat_key))
                    .and(message::Column::Content.eq(&checked))
                    .and(message::Column::UserKey.eq(&user_key))
                    .and(message::Column::ParentId.is_in(id_vec.clone())),
            )
            .into_tuple()
            .all(&core.db)
            .await?;

        let mut uncheck_set = BTreeSet::from_iter(id_vec);
        for checked_id in checked_id_vec {
            uncheck_set.remove(&checked_id);
        }

        Ok(uncheck_set.len() as u64)
    }

    pub async fn latest_message(
        &self,
        core: &SignerDaemonCore,
    ) -> crate::DaemonResult<Option<MessageVO>> {
        let chat_variant = self.chat_variant();
        let chat_key = self.chat_key(core).await?;

        let message = match self {
            ChatVO::Private { .. } => {
                message::Entity::find()
                    .filter(
                        message::Column::ChatVariant
                            .eq(chat_variant)
                            .and(message::Column::ChatKey.eq(chat_key))
                            .and(
                                message::Column::ContentType
                                    .ne(MessageContent::Check(Null {}).ty()),
                            ),
                    )
                    .order_by_desc(message::Column::CreateTime)
                    .one(&core.db)
                    .await?
            }
        };

        let message_vo = match message {
            None => None,
            Some(message) => Some(MessageVO::from_model(&core.db, &message).await?),
        };

        Ok(message_vo)
    }

    pub async fn destinations(&self, daemon: &SignerDaemon) -> crate::DaemonResult<Vec<String>> {
        match self {
            ChatVO::Private { .. } => {
                let chat_key = self.chat_key(&daemon.core).await?;
                let user_vo = daemon.store.user.get(&chat_key).await?;
                let user_vo = match user_vo {
                    None => {
                        return Err(crate::DaemonError::Signer(crate::SignerError::Msg(
                            "user not found".to_string(),
                        )));
                    }
                    Some(user_vo) => user_vo,
                };
                let public = user_vo.public()?;
                Ok(public.allow_endpoints)
            }
        }
    }

    pub async fn receiver_keys(&self, core: &SignerDaemonCore) -> crate::DaemonResult<Vec<String>> {
        let receiver_keys = match self {
            ChatVO::Private { .. } => {
                let chat_key = self.chat_key(core).await?;
                vec![chat_key]
            }
        };

        Ok(receiver_keys)
    }
}

#[cfg(test)]
mod test {
    use sea_orm::EntityTrait;
    use signer_core::SignerUser;

    use crate::{
        SignerDaemon,
        entity::message,
        model::viewobject::{ChatVO, Envelope, MessageContent, MessageVO, PrivateChatVO},
        signer_remote::SignerRemote,
    };

    #[tokio::test]
    async fn test_uncheck_message_count() -> crate::DaemonResult<()> {
        let alice = SignerUser::generete("alice")?;
        let alice_daemon = SignerDaemon::from_memory(&alice, "test-uncheck").await?;

        let bob = SignerUser::generete("bob")?;
        let bob_daemon = SignerDaemon::from_memory(&bob, "test-uncheck").await?;

        let remote = SignerRemote::new("http://localhost:8080");
        remote.ping(&alice).await?;
        remote.ping(&bob).await?;

        let message = MessageContent::Text(format!("Hello Bob!"));
        let message_id = uuid::Uuid::new_v4().to_string();
        let message = MessageVO {
            chat: ChatVO::Private(PrivateChatVO {
                peers: vec![bob.public.pub_key.clone(), alice.public.pub_key.clone()],
            }),
            id: message_id.clone(),
            parent_id: None,
            parent_user_key: None,
            user_key: alice.public.pub_key.clone(),
            create_time: chrono::Utc::now().timestamp_millis(),
            receiver_keys: vec![bob.public.pub_key.clone()],
            content: message,
        };

        let envelope = Envelope::create(
            &*alice_daemon.read().await,
            &message,
            vec!["http://localhost:8080".to_string()],
        )
        .await?;
        envelope.send(&alice).await?;

        remote.pull_message(&mut *bob_daemon.write().await).await?;

        let uncheck = message
            .chat
            .uncheck_message_count(&bob_daemon.read().await.core)
            .await?;

        assert_eq!(uncheck, 1);

        let check = message
            .create_check_message(&*bob_daemon.read().await)
            .await?;
        bob_daemon.write().await.store.message.put(check).await?;

        let bob_messages = message::Entity::find()
            .all(&bob_daemon.read().await.core.db)
            .await?;
        assert_eq!(bob_messages.len(), 2);

        let uncheck = message
            .chat
            .uncheck_message_count(&bob_daemon.read().await.core)
            .await?;
        assert_eq!(uncheck, 0);

        Ok(())
    }
}