use std::borrow::Cow;
use std::str::FromStr;
use crate::app::bond::{self, BondSlashReason};
use crate::app::context::AppContext;
use crate::db::{
find_dispute_by_order_id, is_assigned_solver, is_dispute_taken_by_admin,
solver_has_write_permission,
};
use crate::lightning::LndConnector;
use crate::nip33::{create_platform_tag_values, new_dispute_event};
use crate::util::{enqueue_order_msg, get_order, send_dm, update_order_event};
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
use sqlx_crud::Crud;
use tracing::{error, info};
pub async fn admin_cancel_action(
ctx: &AppContext,
msg: Message,
event: &UnwrappedMessage,
my_keys: &Keys,
ln_client: &mut LndConnector,
) -> Result<(), MostroError> {
let pool = ctx.pool();
let request_id = msg.get_inner_message_kind().request_id;
let order = get_order(&msg, pool).await?;
match is_assigned_solver(pool, &event.identity.to_string(), order.id).await {
Ok(false) => {
if is_dispute_taken_by_admin(pool, order.id, &my_keys.public_key().to_string()).await? {
return Err(MostroCantDo(CantDoReason::DisputeTakenByAdmin));
} else {
return Err(MostroCantDo(CantDoReason::IsNotYourDispute));
}
}
Err(e) => {
return Err(MostroInternalErr(ServiceError::DbAccessError(
e.to_string(),
)));
}
_ => {}
}
if !solver_has_write_permission(pool, &event.identity.to_string(), order.id).await? {
return Err(MostroCantDo(CantDoReason::NotAuthorized));
}
if order.check_status(Status::CooperativelyCanceled).is_ok() {
enqueue_order_msg(
request_id,
Some(order.id),
Action::CooperativeCancelAccepted,
None,
event.identity,
msg.get_inner_message_kind().trade_index,
)
.await;
return Ok(());
}
if order.check_status(Status::Dispute).is_err() {
return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
}
let bond_resolution = bond::extract_bond_resolution(&msg);
bond::validate_bond_resolution(pool, &order, &bond_resolution).await?;
if order.hash.is_some() {
if let Some(hash) = order.hash.as_ref() {
ln_client.cancel_hold_invoice(hash).await?;
info!("Order Id {}: Funds returned to seller", &order.id);
}
}
let dispute = find_dispute_by_order_id(pool, order.id).await;
let dispute_initiator = match (order.seller_dispute, order.buyer_dispute) {
(true, false) => "seller",
(false, true) => "buyer",
(_, _) => return Err(MostroInternalErr(ServiceError::DisputeEventError)),
};
if let Ok(mut d) = dispute {
let dispute_id = d.id;
d.status = DisputeStatus::SellerRefunded.to_string();
d.update(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
let tags: Tags = Tags::from_list(vec![
Tag::custom(
TagKind::Custom(Cow::Borrowed("s")),
vec![DisputeStatus::SellerRefunded.to_string()],
),
Tag::custom(
TagKind::Custom(std::borrow::Cow::Borrowed("initiator")),
vec![dispute_initiator],
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("y")),
create_platform_tag_values(ctx.settings().mostro.name.as_deref()),
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("z")),
vec!["dispute".to_string()],
),
]);
let event = new_dispute_event(my_keys, "", dispute_id.to_string(), tags)
.map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
info!("Dispute event to be published: {event:#?}");
let client = ctx.nostr_client();
if let Err(e) = client.send_event(&event).await {
error!("Failed to send dispute status event: {}", e);
}
}
let order_updated = update_order_event(my_keys, Status::CanceledByAdmin, &order)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
order_updated
.update(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
let message = Message::new_order(
Some(order.id),
request_id,
msg.get_inner_message_kind().trade_index,
Action::AdminCanceled,
None,
);
let message = message
.as_json()
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
send_dm(event.sender, my_keys, &message, None)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) {
(Some(seller), Some(buyer)) => (
PublicKey::from_str(seller.as_str())
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
PublicKey::from_str(buyer.as_str())
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
),
(None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
(_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
};
send_dm(seller_pubkey, my_keys, &message, None)
.await
.map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
send_dm(buyer_pubkey, my_keys, &message, None)
.await
.map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
if let Err(e) = bond::apply_bond_resolution(
pool,
ln_client,
&order,
&bond_resolution,
BondSlashReason::LostDispute,
)
.await
{
tracing::warn!(
order_id = %order.id,
"admin_cancel: bond resolution apply failed: {}", e
);
}
Ok(())
}