zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use actix::Context;
use actix::Handler;
use actix::Message as ActixMessage;

use serde_json::json;

use crate::websocket::webchat::input::Action;
use crate::websocket::webchat::output::Level;
use crate::websocket::webchat::output::OutputMessageType;
use crate::websocket::webchat::server::Server;
use crate::websocket::webchat::services::save_chat_message;
use crate::websocket::webchat::ConnectId;

#[derive(ActixMessage)]
#[rtype(result = "()")]
pub struct SendMessageAction {
    pub id: Option<String>,    // 消息id
    pub connect_id: ConnectId, // from
    pub to: String,            // user id
    pub msg: String,
}

impl SendMessageAction {
    pub fn parse(
        connection_id: &str,
        data: &serde_json::Value,
    ) -> Result<SendMessageAction, String> {
        match get_value(data) {
            Ok((id, to, msg)) => Ok(SendMessageAction {
                id: Some(id),
                connect_id: connection_id.to_owned(),
                to,
                msg,
            }),
            Err(err) => Err(format!("invalid message: error={}", err)),
        }
    }
}

impl Handler<SendMessageAction> for Server {
    type Result = ();

    fn handle(&mut self, msg: SendMessageAction, _ctx: &mut Context<Self>) {
        let _connection_id = msg.connect_id;
        let _to = msg.to;
        let _msg = msg.msg;

        if let Some(from_user) = self.user_infos.get(&_connection_id) {
            let _from_user_id = from_user.user_id().to_owned();
            let _from_user_name = from_user.user_name.to_owned();
            let _user_infos = &mut self.user_infos;
            let _now = chrono::Utc::now().timestamp_millis();

            if _from_user_id == _to {
                // 发给自己的, 不处理
                log::info!(
                    "SendMessageAction-Ignore: _from={}, _to={}, _msg={}",
                    _from_user_name,
                    _to,
                    _msg
                );

                return;
            }

            let _found_connections: Vec<_> = _user_infos
                .iter()
                .filter(|(_, v)| v.user_id() == _to)
                .map(|(k, _)| k)
                .collect();

            if !_found_connections.is_empty() {
                // 在线
                // 1. 保存消息, 标记为已读
                // TODO
                log::info!(
                    "SendMessageAction-Online: _from={}, _to={}, _msg={}",
                    _from_user_name,
                    _to,
                    _msg
                );
                // 2. 将消息投递给用户
                //    -- 通过redis发布消息
                // TODO
            } else {
                // 离线
                // 1. 保存消息, 标记为未读
                //    -- 当用户再次在线时会查询出所有未读消息
                // TODO
                let _mongo = self.ctx.mongo();

                match save_chat_message(_mongo, &_from_user_id, &_to, &_msg, _now) {
                    Ok(_) => {
                        log::info!(
                            "SendMessageAction-Offline: _from={}, _to={}, _msg={}",
                            _from_user_name,
                            _to,
                            _msg
                        );
                    }
                    Err(e) => {
                        log::info!(
                            "SendMessageAction-Error: _from={}, _to={}, error={}",
                            _from_user_name,
                            _to,
                            e
                        );

                        self.send_json(
                            Level::Err,
                            OutputMessageType::Failed,
                            &_connection_id,
                            json! ({"action": Action::SendMessage, "description": "send message failed, try again later."}),
                            msg.id,
                        );
                    }
                }
            }
        }
    }
}

fn get_value(data: &serde_json::Value) -> Result<(String, String, String), String> {
    let id = crate::commons::get_value_as_string(data, "id").unwrap_or(None);
    let to = crate::commons::get_value_as_string(data, "to").unwrap_or(None);
    let msg = crate::commons::get_value_as_string(data, "msg").unwrap_or(None);

    if let (Some(_to), Some(_msg)) = (to, msg) {
        if _to.len() > 64 {
            return Err("`to` invalid".to_string());
        }

        if _msg.len() > 128 {
            return Err("`msg` to long".to_string());
        }

        Ok((
            id.unwrap_or_else(|| uuid::Uuid::now_v7().to_string()),
            _to,
            _msg,
        ))
    } else {
        Err("`to`、`msg` are requried".to_string())
    }
}