signer-daemon 0.2.1

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

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

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

use super::MessageVO;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ChatVO {
  Private { peers: Vec<String> },
}

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

  pub async fn chat_key(
    &self,
    daemon: &SignerDaemon,
  ) -> anyhow::Result<String> {
    match self {
      ChatVO::Private { peers } => {
        let self_key = daemon.user.lock().await.public.pub_key.clone();
        // peers 为 1 时表示是用户向自己发起的私聊,peers 为 2 时表示是用户向他人发起的私聊
        if peers.len() == 0 || peers.len() > 2 {
          anyhow::bail!(
            "private chat peers length must less than 2 and greater than 0"
          );
        }

        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,
    daemon: &SignerDaemon,
  ) -> anyhow::Result<u64> {
    let chat_variant = self.chat_variant();
    let chat_key = self.chat_key(daemon).await?;

    let user_key = daemon.user.lock().await.public.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(&daemon.db)
      .await?;

    let checked = serde_json::to_string(&MessageContent::Check)?;
    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(&daemon.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,
    daemon: &SignerDaemon,
  ) -> anyhow::Result<Option<MessageVO>> {
    let chat_variant = self.chat_variant();
    let chat_key = self.chat_key(daemon).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.ty())),
          )
          .order_by_desc(message::Column::CreateTime)
          .one(&daemon.db)
          .await?
      }
    };

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

    Ok(message_vo)
  }

  pub async fn destinations(
    &self,
    daemon: &SignerDaemon,
  ) -> anyhow::Result<Vec<String>> {
    match self {
      ChatVO::Private { .. } => {
        let chat_key = self.chat_key(daemon).await?;
        let user_vo = daemon.get_user(&chat_key).await?;
        let user_vo = match user_vo {
          None => bail!("user not found"),
          Some(user_vo) => user_vo,
        };
        let public = user_vo.public()?;
        Ok(public.allow_endpoints)
      }
    }
  }

  pub async fn receiver_keys(
    &self,
    daemon: &SignerDaemon,
  ) -> anyhow::Result<Vec<String>> {
    let receiver_keys = match self {
      ChatVO::Private { .. } => {
        let chat_key = self.chat_key(daemon).await?;
        vec![chat_key]
      }
    };

    Ok(receiver_keys)
  }
}

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

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

  #[tokio::test]
  async fn test_uncheck_message_count() -> anyhow::Result<()> {
    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?;

    alice_daemon
      .ping_server("http://localhost:8080/api/signer")
      .await?;
    bob_daemon
      .ping_server("http://localhost:8080/api/signer")
      .await?;

    let message = MessageContent::Text(format!("Hello Bob!"));
    let message_id = uuid::Uuid::new_v4().to_string();
    let message = MessageVO {
      chat: ChatVO::Private {
        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,
      &message,
      vec!["http://localhost:8080/api/signer".to_string()],
    )
    .await?;
    envelope.send(&alice).await?;

    bob_daemon
      .remote_pull_message("http://localhost:8080/api/signer")
      .await?;

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

    let mut rx = bob_daemon.event_receiver.subscribe();

    let check = message.create_check_message(&bob_daemon).await?;
    bob_daemon.put_message(check).await?;

    loop {
      let ev = rx.recv().await?;
      match ev {
        SignerDaemonEvent::ApplyEvent => break,
        _ => {}
      }
    }

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

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

    Ok(())
  }
}