mostro 0.17.5

Lightning Network peer-to-peer nostr platform
use crate::app::context::AppContext;
use crate::db::add_new_user;
use crate::util::send_dm;
use mostro_core::prelude::*;
use mostro_core::user::User;
use nostr_sdk::prelude::*;
use tracing::{error, info};

pub const SOLVER_CATEGORY_READ_ONLY: i64 = 1;
pub const SOLVER_CATEGORY_READ_WRITE: i64 = 2;

fn parse_solver_payload(payload: &Payload) -> Result<(String, i64), MostroError> {
    let raw = match payload {
        Payload::TextMessage(p) => p.trim(),
        _ => return Err(MostroCantDo(CantDoReason::InvalidTextMessage)),
    };

    if raw.is_empty() {
        return Err(MostroCantDo(CantDoReason::InvalidTextMessage));
    }

    let mut parts = raw.split(':');
    let npub = parts
        .next()
        .ok_or(MostroCantDo(CantDoReason::InvalidTextMessage))?
        .trim();

    if npub.is_empty() {
        return Err(MostroCantDo(CantDoReason::InvalidTextMessage));
    }

    let category = match parts.next().map(str::trim) {
        None => SOLVER_CATEGORY_READ_WRITE,
        Some("read") => SOLVER_CATEGORY_READ_ONLY,
        Some("read-write") | Some("write") => SOLVER_CATEGORY_READ_WRITE,
        Some("") | Some(_) => return Err(MostroCantDo(CantDoReason::InvalidParameters)),
    };

    if parts.next().is_some() {
        return Err(MostroCantDo(CantDoReason::InvalidParameters));
    }

    Ok((npub.to_string(), category))
}

pub async fn admin_add_solver_action(
    ctx: &AppContext,
    msg: Message,
    event: &UnwrappedMessage,
    my_keys: &Keys,
) -> Result<(), MostroError> {
    let pool = ctx.pool();
    let request_id = msg.get_inner_message_kind().request_id;

    let inner_message = msg.get_inner_message_kind();
    let payload = inner_message
        .payload
        .as_ref()
        .ok_or(MostroCantDo(CantDoReason::InvalidTextMessage))?;

    if event.identity != my_keys.public_key() {
        return Err(MostroInternalErr(ServiceError::InvalidPubkey));
    }

    let trade_index = inner_message.trade_index.unwrap_or(0);
    let (npubkey, category) = parse_solver_payload(payload)?;
    let public_key = PublicKey::from_bech32(&npubkey)
        .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?;

    let user = User::new(public_key.to_string(), 0, 1, 0, category, trade_index);

    match add_new_user(pool, user).await {
        Ok(r) => info!("Solver added: {} with category {}", r, category),
        Err(ee) => {
            error!("Error creating solver: {:#?}", ee);
            return Err(MostroInternalErr(ServiceError::DbAccessError(
                ee.to_string(),
            )));
        }
    }

    let message = Message::new_dispute(None, request_id, None, Action::AdminAddSolver, None);
    let message = message
        .as_json()
        .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;

    send_dm(event.sender, my_keys, &message, None)
        .await
        .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{parse_solver_payload, SOLVER_CATEGORY_READ_ONLY, SOLVER_CATEGORY_READ_WRITE};
    use mostro_core::error::CantDoReason;
    use mostro_core::message::Payload;

    #[test]
    fn parse_solver_payload_defaults_to_read_write() {
        let (npub, category) =
            parse_solver_payload(&Payload::TextMessage("npub1test".to_string())).unwrap();
        assert_eq!(npub, "npub1test");
        assert_eq!(category, SOLVER_CATEGORY_READ_WRITE);
    }

    #[test]
    fn parse_solver_payload_accepts_read_only() {
        let (_, category) =
            parse_solver_payload(&Payload::TextMessage("npub1test:read".to_string())).unwrap();
        assert_eq!(category, SOLVER_CATEGORY_READ_ONLY);
    }

    #[test]
    fn parse_solver_payload_accepts_read_write_aliases() {
        let (_, category) =
            parse_solver_payload(&Payload::TextMessage("npub1test:read-write".to_string()))
                .unwrap();
        assert_eq!(category, SOLVER_CATEGORY_READ_WRITE);

        let (_, category) =
            parse_solver_payload(&Payload::TextMessage("npub1test:write".to_string())).unwrap();
        assert_eq!(category, SOLVER_CATEGORY_READ_WRITE);
    }

    #[test]
    fn parse_solver_payload_rejects_invalid_permission() {
        let err =
            parse_solver_payload(&Payload::TextMessage("npub1test:admin".to_string())).unwrap_err();
        assert_eq!(
            err,
            mostro_core::error::MostroError::MostroCantDo(CantDoReason::InvalidParameters)
        );
    }

    #[test]
    fn parse_solver_payload_rejects_empty_permission_token() {
        let err =
            parse_solver_payload(&Payload::TextMessage("npub1test:".to_string())).unwrap_err();
        assert_eq!(
            err,
            mostro_core::error::MostroError::MostroCantDo(CantDoReason::InvalidParameters)
        );
    }
}