mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
Store status notifications
This commit is contained in:
parent
705c928b9c
commit
4f2ec020a9
@ -11,7 +11,6 @@ from rest_framework.authtoken.models import TokenProxy
|
||||
from api.logics import Logics
|
||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, Robot
|
||||
from api.utils import objects_to_hyperlinks
|
||||
from api.tasks import send_notification
|
||||
|
||||
admin.site.unregister(Group)
|
||||
admin.site.unregister(User)
|
||||
@ -164,9 +163,6 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||
f"Order {order.id} successfully closed",
|
||||
messages.SUCCESS,
|
||||
)
|
||||
send_notification.delay(
|
||||
order_id=order.id, message="coordinator_cancelled"
|
||||
)
|
||||
else:
|
||||
self.message_user(
|
||||
request,
|
||||
@ -210,7 +206,6 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||
f"Dispute of order {order.id} solved successfully on favor of the maker",
|
||||
messages.SUCCESS,
|
||||
)
|
||||
send_notification.delay(order_id=order.id, message="dispute_closed")
|
||||
|
||||
else:
|
||||
self.message_user(
|
||||
@ -249,7 +244,6 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||
f"Dispute of order {order.id} solved successfully on favor of the taker",
|
||||
messages.SUCCESS,
|
||||
)
|
||||
send_notification.delay(order_id=order.id, message="dispute_closed")
|
||||
|
||||
else:
|
||||
self.message_user(
|
||||
|
@ -617,10 +617,10 @@ class CLNNode:
|
||||
]
|
||||
)
|
||||
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.FAI)
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
print(
|
||||
@ -646,10 +646,10 @@ class CLNNode:
|
||||
)
|
||||
lnpayment.preimage = response.payment_preimage.hex()
|
||||
lnpayment.save(update_fields=["status", "fee", "preimage"])
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.SUC)
|
||||
)
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
order.log(
|
||||
@ -697,10 +697,10 @@ class CLNNode:
|
||||
]
|
||||
)
|
||||
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.FAI)
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
print(
|
||||
@ -737,10 +737,10 @@ class CLNNode:
|
||||
"in_flight",
|
||||
]
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.FAI)
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
order.log(
|
||||
|
@ -594,11 +594,10 @@ class LNDNode:
|
||||
]
|
||||
)
|
||||
|
||||
order.update_status(Order.Status.FAI)
|
||||
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.FAI)
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
str_failure_reason = cls.payment_failure_context[
|
||||
@ -625,10 +624,10 @@ class LNDNode:
|
||||
lnpayment.preimage = response.payment_preimage
|
||||
lnpayment.save(update_fields=["status", "fee", "preimage"])
|
||||
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.SUC)
|
||||
)
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
order.log(
|
||||
@ -671,10 +670,10 @@ class LNDNode:
|
||||
update_fields=["status", "last_routing_time", "in_flight"]
|
||||
)
|
||||
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.FAI)
|
||||
)
|
||||
order.update_status(Order.Status.FAI)
|
||||
order.save(update_fields=["expires_at"])
|
||||
|
||||
order.log(
|
||||
|
@ -8,7 +8,7 @@ from django.utils import timezone
|
||||
|
||||
from api.lightning.node import LNNode
|
||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||
from api.tasks import send_devfund_donation, send_notification, nostr_send_order_event
|
||||
from api.tasks import send_devfund_donation, send_status_notification, send_notification, nostr_send_order_event
|
||||
from api.utils import get_minning_fee, validate_onchain_address, location_country
|
||||
from chat.models import Message
|
||||
|
||||
@ -180,10 +180,10 @@ class Logics:
|
||||
if order.has_range:
|
||||
order.amount = amount
|
||||
order.taker = user
|
||||
order.update_status(Order.Status.TAK)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.TAK)
|
||||
)
|
||||
order.update_status(Order.Status.TAK)
|
||||
order.save(update_fields=["amount", "taker", "expires_at"])
|
||||
order.log(
|
||||
f"Taken by Robot({user.robot.id},{user.username}) for {order.amount} fiat units"
|
||||
@ -267,9 +267,9 @@ class Logics:
|
||||
return False
|
||||
|
||||
elif order.status == Order.Status.WFB:
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.expiry_reason = Order.ExpiryReasons.NMBOND
|
||||
cls.cancel_bond(order.maker_bond)
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.save(update_fields=["expiry_reason"])
|
||||
|
||||
order.log("Order expired while waiting for maker bond")
|
||||
@ -279,10 +279,9 @@ class Logics:
|
||||
|
||||
elif order.status in [Order.Status.PUB, Order.Status.PAU]:
|
||||
cls.return_bond(order.maker_bond)
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.expiry_reason = Order.ExpiryReasons.NTAKEN
|
||||
order.save(update_fields=["expiry_reason"])
|
||||
send_notification.delay(order_id=order.id, message="order_expired_untaken")
|
||||
order.update_status(Order.Status.EXP)
|
||||
|
||||
order.log("Order expired while public or paused")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
@ -307,8 +306,8 @@ class Logics:
|
||||
cls.settle_bond(order.maker_bond)
|
||||
cls.settle_bond(order.taker_bond)
|
||||
cls.cancel_escrow(order)
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.expiry_reason = Order.ExpiryReasons.NESINV
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.save(update_fields=["expiry_reason"])
|
||||
|
||||
order.log(
|
||||
@ -330,8 +329,8 @@ class Logics:
|
||||
cls.cancel_escrow(order)
|
||||
except Exception:
|
||||
pass
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.expiry_reason = Order.ExpiryReasons.NESCRO
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.save(update_fields=["expiry_reason"])
|
||||
# Reward taker with part of the maker bond
|
||||
cls.add_slashed_rewards(order, order.maker_bond, order.taker_bond)
|
||||
@ -352,7 +351,9 @@ class Logics:
|
||||
pass
|
||||
taker_bond = order.taker_bond
|
||||
cls.publish_order(order)
|
||||
send_notification.delay(order_id=order.id, message="order_published")
|
||||
send_status_notification.delay(
|
||||
order_id=order.id, status=Order.Status.PUB
|
||||
)
|
||||
# Reward maker with part of the taker bond
|
||||
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
|
||||
|
||||
@ -371,8 +372,8 @@ class Logics:
|
||||
cls.settle_bond(order.maker_bond)
|
||||
cls.return_bond(order.taker_bond)
|
||||
cls.return_escrow(order)
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.expiry_reason = Order.ExpiryReasons.NINVOI
|
||||
order.update_status(Order.Status.EXP)
|
||||
order.save(update_fields=["expiry_reason"])
|
||||
# Reward taker with part of the maker bond
|
||||
cls.add_slashed_rewards(order, order.maker_bond, order.taker_bond)
|
||||
@ -389,7 +390,9 @@ class Logics:
|
||||
cls.return_escrow(order)
|
||||
taker_bond = order.taker_bond
|
||||
cls.publish_order(order)
|
||||
send_notification.delay(order_id=order.id, message="order_published")
|
||||
send_status_notification.delay(
|
||||
order_id=order.id, status=Order.Status.PUB
|
||||
)
|
||||
# Reward maker with part of the taker bond
|
||||
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
|
||||
|
||||
@ -498,7 +501,6 @@ class Logics:
|
||||
seconds=order.t_to_expire(Order.Status.DIS)
|
||||
)
|
||||
order.save(update_fields=["is_disputed", "expires_at"])
|
||||
send_notification.delay(order_id=order.id, message="dispute_opened")
|
||||
|
||||
return True
|
||||
|
||||
@ -530,10 +532,10 @@ class Logics:
|
||||
cls.settle_bond(order.taker_bond)
|
||||
|
||||
order.is_disputed = True
|
||||
order.update_status(Order.Status.DIS)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.DIS)
|
||||
)
|
||||
order.update_status(Order.Status.DIS)
|
||||
order.save(update_fields=["is_disputed", "expires_at"])
|
||||
|
||||
# User could be None if a dispute is open automatically due to time expiration.
|
||||
@ -548,7 +550,6 @@ class Logics:
|
||||
).append(str(order.id))
|
||||
robot.save(update_fields=["num_disputes", "orders_disputes_started"])
|
||||
|
||||
send_notification.delay(order_id=order.id, message="dispute_opened")
|
||||
order.log(
|
||||
f"Dispute was opened {f'by Robot({user.robot.id},{user.username})' if user else ''}"
|
||||
)
|
||||
@ -587,10 +588,10 @@ class Logics:
|
||||
None,
|
||||
"",
|
||||
]:
|
||||
order.update_status(Order.Status.WFR)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.WFR)
|
||||
)
|
||||
order.update_status(Order.Status.WFR)
|
||||
order.save(update_fields=["status", "expires_at"])
|
||||
|
||||
order.log(
|
||||
@ -944,11 +945,10 @@ class Logics:
|
||||
def move_state_updated_payout_method(cls, order):
|
||||
# If the order status is 'Waiting for invoice'. Move forward to 'chat'
|
||||
if order.status == Order.Status.WFI:
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA)
|
||||
)
|
||||
send_notification.delay(order_id=order.id, message="fiat_exchange_starts")
|
||||
order.update_status(Order.Status.CHA)
|
||||
|
||||
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
||||
elif order.status == Order.Status.WF2:
|
||||
@ -958,22 +958,19 @@ class Logics:
|
||||
|
||||
# If the escrow is locked move to Chat.
|
||||
elif order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA)
|
||||
)
|
||||
send_notification.delay(
|
||||
order_id=order.id, message="fiat_exchange_starts"
|
||||
)
|
||||
order.update_status(Order.Status.CHA)
|
||||
else:
|
||||
order.update_status(Order.Status.WFE)
|
||||
|
||||
# If the order status is 'Failed Routing'. Retry payment.
|
||||
elif order.status == Order.Status.FAI:
|
||||
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
|
||||
order.update_status(Order.Status.PAY)
|
||||
order.payout.status = LNPayment.Status.FLIGHT
|
||||
order.payout.routing_attempts = 0
|
||||
order.update_status(Order.Status.PAY)
|
||||
order.payout.save(update_fields=["status", "routing_attempts"])
|
||||
|
||||
order.save(update_fields=["expires_at"])
|
||||
@ -1034,10 +1031,6 @@ class Logics:
|
||||
# Return the maker bond (Maker gets returned the bond for cancelling public order)
|
||||
if cls.return_bond(order.maker_bond):
|
||||
order.update_status(Order.Status.UCA)
|
||||
send_notification.delay(
|
||||
order_id=order.id, message="public_order_cancelled"
|
||||
)
|
||||
|
||||
order.log("Order cancelled by maker while public or paused")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
|
||||
@ -1054,9 +1047,6 @@ class Logics:
|
||||
if cls.return_bond(order.maker_bond):
|
||||
cls.cancel_bond(order.taker_bond)
|
||||
order.update_status(Order.Status.UCA)
|
||||
send_notification.delay(
|
||||
order_id=order.id, message="public_order_cancelled"
|
||||
)
|
||||
|
||||
order.log("Order cancelled by maker before the taker locked the bond")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
@ -1123,7 +1113,9 @@ class Logics:
|
||||
if valid:
|
||||
taker_bond = order.taker_bond
|
||||
cls.publish_order(order)
|
||||
send_notification.delay(order_id=order.id, message="order_published")
|
||||
send_status_notification.delay(
|
||||
order_id=order.id, status=Order.Status.PUB
|
||||
)
|
||||
# Reward maker with part of the taker bond
|
||||
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
|
||||
|
||||
@ -1197,7 +1189,6 @@ class Logics:
|
||||
cls.return_bond(order.taker_bond)
|
||||
cls.return_escrow(order)
|
||||
order.update_status(Order.Status.CCA)
|
||||
send_notification.delay(order_id=order.id, message="collaborative_cancelled")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
@ -1210,7 +1201,6 @@ class Logics:
|
||||
|
||||
@classmethod
|
||||
def publish_order(cls, order):
|
||||
order.status = Order.Status.PUB
|
||||
order.expires_at = order.created_at + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.PUB)
|
||||
)
|
||||
@ -1226,6 +1216,7 @@ class Logics:
|
||||
order.payout = None
|
||||
order.payout_tx = None
|
||||
|
||||
order.update_status(Order.Status.PUB)
|
||||
order.save() # update all fields
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
@ -1344,7 +1335,6 @@ class Logics:
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.WF2)
|
||||
)
|
||||
order.status = Order.Status.WF2
|
||||
order.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
@ -1371,10 +1361,11 @@ class Logics:
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
send_notification.delay(order_id=order.id, message="order_taken_confirmed")
|
||||
|
||||
order.update_status(Order.Status.WF2)
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
|
||||
order.log(
|
||||
f"<b>Contract formalized.</b> Maker: Robot({order.maker.robot.id},{order.maker}). Taker: Robot({order.taker.robot.id},{order.taker}). API median price {order.currency.exchange_rate} {dict(Currency.currency_choices)[order.currency.currency]}/BTC. Premium is {order.premium}%. Contract size {order.last_satoshis} Sats"
|
||||
)
|
||||
@ -1471,12 +1462,11 @@ class Logics:
|
||||
order.update_status(Order.Status.WFI)
|
||||
# If status is 'Waiting for invoice' move to Chat
|
||||
elif order.status == Order.Status.WFE:
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
seconds=order.t_to_expire(Order.Status.CHA)
|
||||
)
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.save(update_fields=["expires_at"])
|
||||
send_notification.delay(order_id=order.id, message="fiat_exchange_starts")
|
||||
|
||||
@classmethod
|
||||
def gen_escrow_hold_invoice(cls, order, user):
|
||||
@ -1644,11 +1634,10 @@ class Logics:
|
||||
order.payout.status = LNPayment.Status.FLIGHT
|
||||
order.payout.save(update_fields=["status"])
|
||||
|
||||
order.update_status(Order.Status.PAY)
|
||||
order.contract_finalization_time = timezone.now()
|
||||
order.update_status(Order.Status.PAY)
|
||||
order.save(update_fields=["contract_finalization_time"])
|
||||
|
||||
send_notification.delay(order_id=order.id, message="trade_successful")
|
||||
order.log("<b>Paying buyer invoice</b>")
|
||||
return True
|
||||
|
||||
@ -1661,11 +1650,10 @@ class Logics:
|
||||
order.payout_tx.status = OnchainPayment.Status.QUEUE
|
||||
order.payout_tx.save(update_fields=["status"])
|
||||
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.contract_finalization_time = timezone.now()
|
||||
order.update_status(Order.Status.SUC)
|
||||
order.save(update_fields=["contract_finalization_time"])
|
||||
|
||||
send_notification.delay(order_id=order.id, message="trade_successful")
|
||||
order.log("<b>Paying buyer onchain address</b>")
|
||||
return True
|
||||
|
||||
@ -1679,8 +1667,8 @@ class Logics:
|
||||
if order.status == Order.Status.CHA or order.status == Order.Status.FSE:
|
||||
# If buyer mark fiat sent
|
||||
if cls.is_buyer(order, user):
|
||||
order.update_status(Order.Status.FSE)
|
||||
order.is_fiat_sent = True
|
||||
order.update_status(Order.Status.FSE)
|
||||
order.save(update_fields=["is_fiat_sent"])
|
||||
|
||||
order.log("Buyer confirmed 'fiat sent'")
|
||||
@ -1744,9 +1732,9 @@ class Logics:
|
||||
return False, {
|
||||
"bad_request": "Only orders in Chat and with fiat sent confirmed can be reverted."
|
||||
}
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.is_fiat_sent = False
|
||||
order.reverted_fiat_sent = True
|
||||
order.update_status(Order.Status.CHA)
|
||||
order.save(update_fields=["is_fiat_sent", "reverted_fiat_sent"])
|
||||
|
||||
order.log(
|
||||
|
@ -8,7 +8,7 @@ from django.utils import timezone
|
||||
from api.lightning.node import LNNode
|
||||
from api.logics import Logics
|
||||
from api.models import LNPayment, OnchainPayment, Order
|
||||
from api.tasks import follow_send_payment, send_notification
|
||||
from api.tasks import follow_send_payment, send_status_notification
|
||||
|
||||
|
||||
def is_same_status(a: LNPayment.Status, b: LNPayment.Status) -> bool:
|
||||
@ -229,8 +229,8 @@ class Command(BaseCommand):
|
||||
if hasattr(lnpayment, "order_made"):
|
||||
lnpayment.order_made.log("Maker bond <b>locked</b>")
|
||||
Logics.publish_order(lnpayment.order_made)
|
||||
send_notification.delay(
|
||||
order_id=lnpayment.order_made.id, message="order_published"
|
||||
send_status_notification.delay(
|
||||
order_id=lnpayment.order_made.id, status=Order.Status.PUB
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -6,8 +6,8 @@ from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from api.models import Robot
|
||||
from api.notifications import Notifications
|
||||
from api.utils import get_session
|
||||
from api.tasks import send_telegram_notification
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -17,7 +17,6 @@ class Command(BaseCommand):
|
||||
bot_token = config("TELEGRAM_TOKEN")
|
||||
updates_url = f"https://api.telegram.org/bot{bot_token}/getUpdates"
|
||||
session = get_session()
|
||||
notifications = Notifications()
|
||||
|
||||
def handle(self, *args, **options):
|
||||
offset = 0
|
||||
@ -49,7 +48,7 @@ class Command(BaseCommand):
|
||||
continue
|
||||
parts = message.split(" ")
|
||||
if len(parts) < 2:
|
||||
self.notifications.send_telegram_message(
|
||||
send_telegram_notification.delay(
|
||||
result["message"]["from"]["id"],
|
||||
'You must enable the notifications bot using the RoboSats client. Click on your "Robot robot" -> "Enable Telegram" and follow the link or scan the QR code.',
|
||||
)
|
||||
@ -57,7 +56,7 @@ class Command(BaseCommand):
|
||||
token = parts[-1]
|
||||
robot = Robot.objects.filter(telegram_token=token).first()
|
||||
if not robot:
|
||||
self.notifications.send_telegram_message(
|
||||
send_telegram_notification.delay(
|
||||
result["message"]["from"]["id"],
|
||||
f'Wops, invalid token! There is no Robot with telegram chat token "{token}"',
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ from django.db import models
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from api.tasks import send_notification
|
||||
from api.tasks import send_status_notification
|
||||
|
||||
if config("TESTING", cast=bool, default=False):
|
||||
import random
|
||||
@ -350,8 +350,7 @@ class Order(models.Model):
|
||||
self.log(
|
||||
f"Order state went from {old_status}: <i>{Order.Status(old_status).label}</i> to {new_status}: <i>{Order.Status(new_status).label}</i>"
|
||||
)
|
||||
if new_status == Order.Status.FAI:
|
||||
send_notification.delay(order_id=self.id, message="lightning_failed")
|
||||
send_status_notification.delay(order_id=self.id, status=self.status)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Order)
|
||||
|
@ -57,6 +57,18 @@ class Notifications:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def status_change(self, order):
|
||||
Notification.objects.create(
|
||||
title="", description="", robot=order.maker.robot, order=order
|
||||
)
|
||||
|
||||
if order.taker:
|
||||
Notification.objects.create(
|
||||
title="", description="", robot=order.taker.robot, order=order
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def welcome(self, user):
|
||||
"""User enabled Telegram Notifications"""
|
||||
lang = user.robot.telegram_lang_code
|
||||
@ -215,11 +227,6 @@ class Notifications:
|
||||
|
||||
return
|
||||
|
||||
def coordinator_cancelled(self, order):
|
||||
title = f"🛠️ Your order with ID {order.id} has been cancelled by the coordinator {config('COORDINATOR_ALIAS', cast=str, default='NoAlias')} for the upcoming maintenance stop."
|
||||
self.send_message(order, order.maker.robot, title)
|
||||
return
|
||||
|
||||
def dispute_closed(self, order):
|
||||
lang = order.maker.robot.telegram_lang_code
|
||||
if order.status == Order.Status.MLD:
|
||||
|
108
api/tasks.py
108
api/tasks.py
@ -251,7 +251,6 @@ def cache_market():
|
||||
print("SOFT LIMIT REACHED. Could not fetch current external market prices.")
|
||||
return
|
||||
|
||||
|
||||
@shared_task(name="", ignore_result=True, time_limit=120)
|
||||
def nostr_send_order_event(order_id=None):
|
||||
if order_id:
|
||||
@ -265,63 +264,70 @@ def nostr_send_order_event(order_id=None):
|
||||
|
||||
return
|
||||
|
||||
|
||||
@shared_task(name="send_notification", ignore_result=True, time_limit=120)
|
||||
def send_notification(order_id=None, chat_message_id=None, message=None):
|
||||
if order_id:
|
||||
@shared_task(name="send_chat_notification", ignore_result=True, time_limit=120)
|
||||
def send_chat_notification(message_id=None, order_id=None):
|
||||
if message_id and order_id:
|
||||
from api.models import Order
|
||||
from chat.models import Message
|
||||
from api.notifications import Notifications
|
||||
|
||||
order = Order.objects.get(id=order_id)
|
||||
elif chat_message_id:
|
||||
from chat.models import Message
|
||||
chat_message = Message.objects.get(id=message_id)
|
||||
notifications = Notifications()
|
||||
|
||||
chat_message = Message.objects.get(id=chat_message_id)
|
||||
order = chat_message.order
|
||||
|
||||
from api.notifications import Notifications
|
||||
|
||||
notifications = Notifications()
|
||||
|
||||
if message == "welcome":
|
||||
notifications.welcome(order)
|
||||
|
||||
elif message == "order_expired_untaken":
|
||||
notifications.order_expired_untaken(order)
|
||||
|
||||
elif message == "trade_successful":
|
||||
notifications.trade_successful(order)
|
||||
|
||||
elif message == "public_order_cancelled":
|
||||
notifications.public_order_cancelled(order)
|
||||
|
||||
elif message == "taker_expired_b4bond":
|
||||
notifications.taker_expired_b4bond(order)
|
||||
|
||||
elif message == "order_published":
|
||||
notifications.order_published(order)
|
||||
|
||||
elif message == "order_taken_confirmed":
|
||||
notifications.order_taken_confirmed(order)
|
||||
|
||||
elif message == "fiat_exchange_starts":
|
||||
notifications.fiat_exchange_starts(order)
|
||||
|
||||
elif message == "dispute_opened":
|
||||
notifications.dispute_opened(order)
|
||||
|
||||
elif message == "collaborative_cancelled":
|
||||
notifications.collaborative_cancelled(order)
|
||||
|
||||
elif message == "new_chat_message":
|
||||
notifications.new_chat_message(order, chat_message)
|
||||
|
||||
elif message == "coordinator_cancelled":
|
||||
notifications.coordinator_cancelled(order)
|
||||
|
||||
elif message == "dispute_closed":
|
||||
notifications.dispute_closed(order)
|
||||
@shared_task(name="send_telegram_notification", ignore_result=True, time_limit=120)
|
||||
def send_telegram_notification(chat_id=None, message=None):
|
||||
if chat_id:
|
||||
from api.notifications import Notifications
|
||||
|
||||
elif message == "lightning_failed":
|
||||
notifications.lightning_failed(order)
|
||||
notifications = Notifications()
|
||||
|
||||
notifications.send_telegram_message(chat_id, message)
|
||||
|
||||
|
||||
@shared_task(name="send_status_notification", ignore_result=True, time_limit=120)
|
||||
def send_status_notification(order_id=None, status=None):
|
||||
if order_id:
|
||||
from api.models import Order
|
||||
from api.notifications import Notifications
|
||||
|
||||
order = Order.objects.get(id=order_id)
|
||||
notifications = Notifications()
|
||||
|
||||
if status == Order.Status.EXP:
|
||||
notifications.order_expired_untaken(order)
|
||||
|
||||
elif status == Order.Status.PAY or status == Order.Status.SUC:
|
||||
notifications.trade_successful(order)
|
||||
|
||||
elif status == Order.Status.UCA:
|
||||
notifications.public_order_cancelled(order)
|
||||
|
||||
elif status == Order.Status.PUB:
|
||||
notifications.order_published(order)
|
||||
|
||||
elif status == Order.Status.WF2:
|
||||
notifications.order_taken_confirmed(order)
|
||||
|
||||
elif status == Order.Status.CHA:
|
||||
notifications.fiat_exchange_starts(order)
|
||||
|
||||
elif status == Order.Status.DIS:
|
||||
notifications.dispute_opened(order)
|
||||
|
||||
elif status == Order.Status.CCA:
|
||||
notifications.collaborative_cancelled(order)
|
||||
|
||||
elif status == Order.Status.TLD or status == Order.Status.MLD:
|
||||
notifications.dispute_closed(order)
|
||||
|
||||
elif status == Order.Status.FAI:
|
||||
notifications.lightning_failed(order)
|
||||
|
||||
else:
|
||||
notifications.status_change(order)
|
||||
|
||||
return
|
||||
|
@ -4,7 +4,7 @@ from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
|
||||
from api.models import Order
|
||||
from api.tasks import send_notification
|
||||
from api.tasks import send_chat_notification
|
||||
from chat.models import ChatRoom, Message
|
||||
|
||||
|
||||
@ -85,7 +85,8 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
|
||||
)
|
||||
|
||||
# send Telegram notification for new message (if conditions apply)
|
||||
send_notification.delay(chat_message_id=msg_obj.id, message="new_chat_message")
|
||||
send_chat_notification.delay(message_id=msg_obj.id, order_id=order.id)
|
||||
|
||||
return msg_obj
|
||||
|
||||
@database_sync_to_async
|
||||
|
@ -11,7 +11,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from api.models import Order
|
||||
from api.tasks import send_notification
|
||||
from api.tasks import send_chat_notification
|
||||
from chat.models import ChatRoom, Message
|
||||
from chat.serializers import ChatSerializer, InMessageSerializer, PostMessageSerializer
|
||||
|
||||
@ -179,9 +179,7 @@ class ChatView(viewsets.ViewSet):
|
||||
)
|
||||
|
||||
# send Telegram notification for new message (if conditions apply)
|
||||
send_notification.delay(
|
||||
chat_message_id=new_message.id, message="new_chat_message"
|
||||
)
|
||||
send_chat_notification.delay(message_id=new_message.id, order_id=order.id)
|
||||
|
||||
# Send websocket message
|
||||
if chatroom.maker == request.user:
|
||||
|
@ -560,6 +560,21 @@ class TradeTest(BaseAPITestCase):
|
||||
trade.publish_order()
|
||||
trade.take_order()
|
||||
trade.lock_taker_bond()
|
||||
|
||||
maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname")
|
||||
taker_nick = read_file(f"tests/robots/{trade.taker_index}/nickname")
|
||||
|
||||
maker_headers = trade.get_robot_auth(trade.maker_index)
|
||||
response = self.client.get(reverse("notifications"), **maker_headers)
|
||||
self.assertResponse(response)
|
||||
notifications_data = list(response.json())
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
# Does not receive notification because user is online
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"✅ Hey {maker_nick}, your order was taken by {taker_nick}!🥳",
|
||||
)
|
||||
|
||||
trade.lock_escrow(trade.taker_index)
|
||||
trade.submit_payout_invoice(trade.maker_index)
|
||||
|
||||
@ -571,7 +586,6 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertEqual(data["status_message"], Order.Status(Order.Status.CHA).label)
|
||||
self.assertFalse(data["is_fiat_sent"])
|
||||
|
||||
maker_headers = trade.get_robot_auth(trade.maker_index)
|
||||
maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname")
|
||||
response = self.client.get(reverse("notifications"), **maker_headers)
|
||||
self.assertResponse(response)
|
||||
@ -845,6 +859,16 @@ class TradeTest(BaseAPITestCase):
|
||||
|
||||
self.assert_order_logs(data["id"])
|
||||
|
||||
maker_headers = trade.get_robot_auth(trade.maker_index)
|
||||
response = self.client.get(reverse("notifications"), **maker_headers)
|
||||
self.assertResponse(response)
|
||||
notifications_data = list(response.json())
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.",
|
||||
)
|
||||
|
||||
def test_public_order_expires(self):
|
||||
"""
|
||||
Tests the expiration of a public order
|
||||
@ -917,6 +941,16 @@ class TradeTest(BaseAPITestCase):
|
||||
|
||||
self.assert_order_logs(data["id"])
|
||||
|
||||
maker_headers = trade.get_robot_auth(trade.maker_index)
|
||||
response = self.client.get(reverse("notifications"), **maker_headers)
|
||||
self.assertResponse(response)
|
||||
notifications_data = list(response.json())
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.",
|
||||
)
|
||||
|
||||
def test_escrow_locked_expires(self):
|
||||
"""
|
||||
Tests the expiration of a public order
|
||||
@ -953,6 +987,16 @@ class TradeTest(BaseAPITestCase):
|
||||
|
||||
self.assert_order_logs(data["id"])
|
||||
|
||||
maker_headers = trade.get_robot_auth(trade.maker_index)
|
||||
response = self.client.get(reverse("notifications"), **maker_headers)
|
||||
self.assertResponse(response)
|
||||
notifications_data = list(response.json())
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.",
|
||||
)
|
||||
|
||||
def test_chat(self):
|
||||
"""
|
||||
Tests the chatting REST functionality
|
||||
@ -1090,7 +1134,7 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"⚖️ Hey {data['maker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
||||
f"⚖️ Hey {data['maker_nick']}, you lost the dispute on your order with ID {str(trade.order_id)}.",
|
||||
)
|
||||
taker_headers = trade.get_robot_auth(trade.taker_index)
|
||||
response = self.client.get(reverse("notifications"), **taker_headers)
|
||||
@ -1099,7 +1143,7 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
||||
f"⚖️ Hey {data['taker_nick']}, you won the dispute on your order with ID {str(trade.order_id)}.",
|
||||
)
|
||||
|
||||
def test_order_expires_after_only_maker_messaged(self):
|
||||
@ -1151,7 +1195,7 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"⚖️ Hey {data['maker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
||||
f"⚖️ Hey {data['maker_nick']}, you won the dispute on your order with ID {str(trade.order_id)}.",
|
||||
)
|
||||
taker_headers = trade.get_robot_auth(trade.taker_index)
|
||||
response = self.client.get(reverse("notifications"), **taker_headers)
|
||||
@ -1160,7 +1204,7 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||
self.assertEqual(
|
||||
notifications_data[0]["title"],
|
||||
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
||||
f"⚖️ Hey {data['taker_nick']}, you lost the dispute on your order with ID {str(trade.order_id)}.",
|
||||
)
|
||||
|
||||
# def test_dispute_closed_maker_wins(self):
|
||||
|
@ -269,7 +269,7 @@ def pay_invoice(node_name, invoice):
|
||||
node = get_node(node_name)
|
||||
data = {"payment_request": invoice}
|
||||
try:
|
||||
response = requests.post(
|
||||
requests.post(
|
||||
f'http://localhost:{node["port"]}/v1/channels/transactions',
|
||||
json=data,
|
||||
headers=node["headers"],
|
||||
@ -277,7 +277,6 @@ def pay_invoice(node_name, invoice):
|
||||
# 0.4s is enough for LND to CLN hodl ACCEPT
|
||||
timeout=0.2 if LNVENDOR == "LND" else 1,
|
||||
)
|
||||
print(response.json())
|
||||
except ReadTimeout:
|
||||
# Request to pay hodl invoice has timed out: that's good!
|
||||
return
|
||||
|
@ -5,7 +5,11 @@ from django.urls import reverse
|
||||
from api.management.commands.clean_orders import Command as CleanOrders
|
||||
from api.management.commands.follow_invoices import Command as FollowInvoices
|
||||
from api.models import Order
|
||||
from api.tasks import follow_send_payment, send_notification
|
||||
from api.tasks import (
|
||||
follow_send_payment,
|
||||
send_status_notification,
|
||||
send_chat_notification,
|
||||
)
|
||||
from tests.utils.node import (
|
||||
add_invoice,
|
||||
create_address,
|
||||
@ -111,7 +115,7 @@ class Trade:
|
||||
headers = self.get_robot_auth(robot_index, first_encounter)
|
||||
self.response = self.client.get(path + params, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def cancel_order(self, robot_index=1):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -119,14 +123,14 @@ class Trade:
|
||||
body = {"action": "cancel"}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_chat_notification.delay", send_chat_notification)
|
||||
def send_chat_message(self, message, robot_index=1):
|
||||
path = reverse("chat")
|
||||
headers = self.get_robot_auth(robot_index)
|
||||
body = {"PGP_message": message, "order_id": self.order_id, "offset": 0}
|
||||
self.response = self.client.post(path, data=body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def pause_order(self, robot_index=1):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -134,13 +138,13 @@ class Trade:
|
||||
body = {"action": "pause"}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def follow_hold_invoices(self):
|
||||
# A background thread checks every 5 second the status of invoices. We invoke directly during test.
|
||||
follower = FollowInvoices()
|
||||
follower.follow_hold_invoices()
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def clean_orders(self):
|
||||
# A background thread checks every 5 second order expirations. We invoke directly during test.
|
||||
cleaner = CleanOrders()
|
||||
@ -155,7 +159,7 @@ class Trade:
|
||||
generate_blocks(create_address("robot"), 1)
|
||||
wait_nodes_sync()
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def publish_order(self):
|
||||
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
|
||||
self.get_order()
|
||||
@ -170,7 +174,7 @@ class Trade:
|
||||
# Get order
|
||||
self.get_order()
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def take_order(self):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -178,7 +182,7 @@ class Trade:
|
||||
body = {"action": "take", "amount": self.take_amount}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def lock_taker_bond(self):
|
||||
# Takers's first order fetch. Should trigger maker bond hold invoice generation.
|
||||
self.get_order(self.taker_index)
|
||||
@ -193,7 +197,7 @@ class Trade:
|
||||
# Get order
|
||||
self.get_order(self.taker_index)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def lock_escrow(self, robot_index):
|
||||
# Takers's order fetch. Should trigger trade escrow bond hold invoice generation.
|
||||
self.get_order(robot_index)
|
||||
@ -208,7 +212,7 @@ class Trade:
|
||||
# Get order
|
||||
self.get_order()
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def submit_payout_address(self, robot_index=1):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -227,7 +231,7 @@ class Trade:
|
||||
}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def submit_payout_invoice(self, robot_index=1, routing_budget=0):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -249,7 +253,7 @@ class Trade:
|
||||
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def confirm_fiat(self, robot_index=1):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -257,7 +261,7 @@ class Trade:
|
||||
body = {"action": "confirm"}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def undo_confirm_sent(self, robot_index=1):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={self.order_id}"
|
||||
@ -265,14 +269,14 @@ class Trade:
|
||||
body = {"action": "undo_confirm"}
|
||||
self.response = self.client.post(path + params, body, **headers)
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def expire_order(self):
|
||||
# Change order expiry to now
|
||||
order = Order.objects.get(id=self.order_id)
|
||||
order.expires_at = datetime.now()
|
||||
order.save()
|
||||
|
||||
@patch("api.tasks.send_notification.delay", send_notification)
|
||||
@patch("api.tasks.send_status_notification.delay", send_status_notification)
|
||||
def change_order_status(self, status):
|
||||
# Change order expiry to now
|
||||
order = Order.objects.get(id=self.order_id)
|
||||
|
Loading…
Reference in New Issue
Block a user