diff --git a/api/admin.py b/api/admin.py
index e9957a1d..0ca8e536 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -11,6 +11,7 @@ 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)
@@ -163,6 +164,9 @@ 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,
@@ -206,6 +210,7 @@ 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(
@@ -244,6 +249,7 @@ 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(
diff --git a/api/lightning/cln.py b/api/lightning/cln.py
index 0c0acba5..82488417 100755
--- a/api/lightning/cln.py
+++ b/api/lightning/cln.py
@@ -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(
diff --git a/api/lightning/lnd.py b/api/lightning/lnd.py
index 96562a68..f914857e 100644
--- a/api/lightning/lnd.py
+++ b/api/lightning/lnd.py
@@ -594,10 +594,11 @@ 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[
@@ -624,10 +625,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(
@@ -670,10 +671,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(
diff --git a/api/logics.py b/api/logics.py
index c5055fbd..8bec242a 100644
--- a/api/logics.py
+++ b/api/logics.py
@@ -8,11 +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_status_notification,
- nostr_send_order_event,
-)
+from api.tasks import send_devfund_donation, send_notification, nostr_send_order_event
from api.utils import get_minning_fee, validate_onchain_address, location_country
from chat.models import Message
@@ -184,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"
@@ -271,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")
@@ -283,9 +279,10 @@ 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"])
- order.update_status(Order.Status.EXP)
+ send_notification.delay(order_id=order.id, message="order_expired_untaken")
order.log("Order expired while public or paused")
order.log("Maker bond was unlocked")
@@ -310,8 +307,8 @@ class Logics:
cls.settle_bond(order.maker_bond)
cls.settle_bond(order.taker_bond)
cls.cancel_escrow(order)
- order.expiry_reason = Order.ExpiryReasons.NESINV
order.update_status(Order.Status.EXP)
+ order.expiry_reason = Order.ExpiryReasons.NESINV
order.save(update_fields=["expiry_reason"])
order.log(
@@ -333,8 +330,8 @@ class Logics:
cls.cancel_escrow(order)
except Exception:
pass
- order.expiry_reason = Order.ExpiryReasons.NESCRO
order.update_status(Order.Status.EXP)
+ order.expiry_reason = Order.ExpiryReasons.NESCRO
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)
@@ -355,9 +352,7 @@ class Logics:
pass
taker_bond = order.taker_bond
cls.publish_order(order)
- send_status_notification.delay(
- order_id=order.id, status=Order.Status.PUB
- )
+ send_notification.delay(order_id=order.id, message="order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
@@ -376,8 +371,8 @@ class Logics:
cls.settle_bond(order.maker_bond)
cls.return_bond(order.taker_bond)
cls.return_escrow(order)
- order.expiry_reason = Order.ExpiryReasons.NINVOI
order.update_status(Order.Status.EXP)
+ order.expiry_reason = Order.ExpiryReasons.NINVOI
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)
@@ -394,9 +389,7 @@ class Logics:
cls.return_escrow(order)
taker_bond = order.taker_bond
cls.publish_order(order)
- send_status_notification.delay(
- order_id=order.id, status=Order.Status.PUB
- )
+ send_notification.delay(order_id=order.id, message="order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
@@ -505,6 +498,7 @@ 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
@@ -536,10 +530,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.
@@ -554,6 +548,7 @@ 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 ''}"
)
@@ -592,10 +587,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(
@@ -949,10 +944,11 @@ 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)
)
- order.update_status(Order.Status.CHA)
+ send_notification.delay(order_id=order.id, message="fiat_exchange_starts")
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
elif order.status == Order.Status.WF2:
@@ -962,19 +958,22 @@ 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)
)
- order.update_status(Order.Status.CHA)
+ send_notification.delay(
+ order_id=order.id, message="fiat_exchange_starts"
+ )
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"])
@@ -1035,6 +1034,10 @@ 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 unlocked")
@@ -1051,6 +1054,9 @@ 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 unlocked")
@@ -1117,9 +1123,7 @@ class Logics:
if valid:
taker_bond = order.taker_bond
cls.publish_order(order)
- send_status_notification.delay(
- order_id=order.id, status=Order.Status.PUB
- )
+ send_notification.delay(order_id=order.id, message="order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(order, taker_bond, order.maker_bond)
@@ -1193,6 +1197,7 @@ 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)
@@ -1205,6 +1210,7 @@ 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)
)
@@ -1220,7 +1226,6 @@ 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)
@@ -1339,6 +1344,7 @@ 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",
@@ -1365,8 +1371,7 @@ class Logics:
)
except Exception:
pass
-
- order.update_status(Order.Status.WF2)
+ send_notification.delay(order_id=order.id, message="order_taken_confirmed")
nostr_send_order_event.delay(order_id=order.id)
@@ -1466,11 +1471,12 @@ 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):
@@ -1638,10 +1644,11 @@ class Logics:
order.payout.status = LNPayment.Status.FLIGHT
order.payout.save(update_fields=["status"])
- order.contract_finalization_time = timezone.now()
order.update_status(Order.Status.PAY)
+ order.contract_finalization_time = timezone.now()
order.save(update_fields=["contract_finalization_time"])
+ send_notification.delay(order_id=order.id, message="trade_successful")
order.log("Paying buyer invoice")
return True
@@ -1654,10 +1661,11 @@ class Logics:
order.payout_tx.status = OnchainPayment.Status.QUEUE
order.payout_tx.save(update_fields=["status"])
- order.contract_finalization_time = timezone.now()
order.update_status(Order.Status.SUC)
+ order.contract_finalization_time = timezone.now()
order.save(update_fields=["contract_finalization_time"])
+ send_notification.delay(order_id=order.id, message="trade_successful")
order.log("Paying buyer onchain address")
return True
@@ -1671,8 +1679,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.is_fiat_sent = True
order.update_status(Order.Status.FSE)
+ order.is_fiat_sent = True
order.save(update_fields=["is_fiat_sent"])
order.log("Buyer confirmed 'fiat sent'")
@@ -1736,9 +1744,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(
diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py
index 93b1f34c..8b4b98c1 100644
--- a/api/management/commands/follow_invoices.py
+++ b/api/management/commands/follow_invoices.py
@@ -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_status_notification
+from api.tasks import follow_send_payment, send_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 locked")
Logics.publish_order(lnpayment.order_made)
- send_status_notification.delay(
- order_id=lnpayment.order_made.id, status=Order.Status.PUB
+ send_notification.delay(
+ order_id=lnpayment.order_made.id, message="order_published"
)
return
diff --git a/api/management/commands/telegram_watcher.py b/api/management/commands/telegram_watcher.py
index 5f57dcae..d626b756 100644
--- a/api/management/commands/telegram_watcher.py
+++ b/api/management/commands/telegram_watcher.py
@@ -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,6 +17,7 @@ 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
@@ -48,7 +49,7 @@ class Command(BaseCommand):
continue
parts = message.split(" ")
if len(parts) < 2:
- send_telegram_notification.delay(
+ self.notifications.send_telegram_message(
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.',
)
@@ -56,7 +57,7 @@ class Command(BaseCommand):
token = parts[-1]
robot = Robot.objects.filter(telegram_token=token).first()
if not robot:
- send_telegram_notification.delay(
+ self.notifications.send_telegram_message(
result["message"]["from"]["id"],
f'Wops, invalid token! There is no Robot with telegram chat token "{token}"',
)
diff --git a/api/models/order.py b/api/models/order.py
index abd34a2e..483e6fa4 100644
--- a/api/models/order.py
+++ b/api/models/order.py
@@ -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_status_notification
+from api.tasks import send_notification
if config("TESTING", cast=bool, default=False):
import random
@@ -350,7 +350,8 @@ class Order(models.Model):
self.log(
f"Order state went from {old_status}: {Order.Status(old_status).label} to {new_status}: {Order.Status(new_status).label}"
)
- send_status_notification.delay(order_id=self.id, status=self.status)
+ if new_status == Order.Status.FAI:
+ send_notification.delay(order_id=self.id, message="lightning_failed")
@receiver(pre_delete, sender=Order)
diff --git a/api/notifications.py b/api/notifications.py
index 7792d6a0..57ec4a42 100644
--- a/api/notifications.py
+++ b/api/notifications.py
@@ -57,18 +57,6 @@ 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
@@ -227,6 +215,11 @@ 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:
diff --git a/api/tasks.py b/api/tasks.py
index fffe0acf..416a5847 100644
--- a/api/tasks.py
+++ b/api/tasks.py
@@ -251,6 +251,7 @@ 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:
@@ -264,70 +265,63 @@ def nostr_send_order_event(order_id=None):
return
-@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)
- chat_message = Message.objects.get(id=message_id)
- notifications = Notifications()
-
- notifications.new_chat_message(order, chat_message)
-
-
-@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
-
- 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):
+@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:
from api.models import Order
- from api.notifications import Notifications
order = Order.objects.get(id=order_id)
- notifications = Notifications()
+ elif chat_message_id:
+ from chat.models import Message
- if status == Order.Status.EXP:
- notifications.order_expired_untaken(order)
+ chat_message = Message.objects.get(id=chat_message_id)
+ order = chat_message.order
- elif status == Order.Status.PAY or status == Order.Status.SUC:
- notifications.trade_successful(order)
+ from api.notifications import Notifications
- elif status == Order.Status.UCA:
- notifications.public_order_cancelled(order)
+ notifications = Notifications()
- elif status == Order.Status.PUB:
- notifications.order_published(order)
+ if message == "welcome":
+ notifications.welcome(order)
- elif status == Order.Status.WF2:
- notifications.order_taken_confirmed(order)
+ elif message == "order_expired_untaken":
+ notifications.order_expired_untaken(order)
- elif status == Order.Status.CHA:
- notifications.fiat_exchange_starts(order)
+ elif message == "trade_successful":
+ notifications.trade_successful(order)
- elif status == Order.Status.DIS:
- notifications.dispute_opened(order)
+ elif message == "public_order_cancelled":
+ notifications.public_order_cancelled(order)
- elif status == Order.Status.CCA:
- notifications.collaborative_cancelled(order)
+ elif message == "taker_expired_b4bond":
+ notifications.taker_expired_b4bond(order)
- elif status == Order.Status.TLD or status == Order.Status.MLD:
- notifications.dispute_closed(order)
+ elif message == "order_published":
+ notifications.order_published(order)
- elif status == Order.Status.FAI:
- notifications.lightning_failed(order)
+ elif message == "order_taken_confirmed":
+ notifications.order_taken_confirmed(order)
- else:
- notifications.status_change(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)
+
+ elif message == "lightning_failed":
+ notifications.lightning_failed(order)
return
diff --git a/chat/consumers.py b/chat/consumers.py
index 1c32ac87..c2443e66 100644
--- a/chat/consumers.py
+++ b/chat/consumers.py
@@ -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_chat_notification
+from api.tasks import send_notification
from chat.models import ChatRoom, Message
@@ -85,8 +85,7 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
)
# send Telegram notification for new message (if conditions apply)
- send_chat_notification.delay(message_id=msg_obj.id, order_id=order.id)
-
+ send_notification.delay(chat_message_id=msg_obj.id, message="new_chat_message")
return msg_obj
@database_sync_to_async
diff --git a/chat/views.py b/chat/views.py
index fc15f73d..ab288169 100644
--- a/chat/views.py
+++ b/chat/views.py
@@ -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_chat_notification
+from api.tasks import send_notification
from chat.models import ChatRoom, Message
from chat.serializers import ChatSerializer, InMessageSerializer, PostMessageSerializer
@@ -179,7 +179,9 @@ class ChatView(viewsets.ViewSet):
)
# send Telegram notification for new message (if conditions apply)
- send_chat_notification.delay(message_id=new_message.id, order_id=order.id)
+ send_notification.delay(
+ chat_message_id=new_message.id, message="new_chat_message"
+ )
# Send websocket message
if chatroom.maker == request.user:
diff --git a/docs/assets/schemas/api-latest.yaml b/docs/assets/schemas/api-latest.yaml
index 2c8e044b..04a0577f 100644
--- a/docs/assets/schemas/api-latest.yaml
+++ b/docs/assets/schemas/api-latest.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: RoboSats REST API
- version: 0.7.0
+ version: 0.6.3
x-logo:
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
backgroundColor: '#FFFFFF'
diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py
index 35ff1fd2..c226b5c0 100644
--- a/tests/test_trade_pipeline.py
+++ b/tests/test_trade_pipeline.py
@@ -560,21 +560,6 @@ 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)
@@ -586,6 +571,7 @@ 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)
@@ -859,16 +845,6 @@ 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
@@ -941,16 +917,6 @@ 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
@@ -987,16 +953,6 @@ 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
@@ -1134,7 +1090,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']}, you lost the dispute on your order with ID {str(trade.order_id)}.",
+ f"⚖️ Hey {data['maker_nick']}, a dispute has been opened 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)
@@ -1143,7 +1099,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']}, you won the dispute on your order with ID {str(trade.order_id)}.",
+ f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
)
def test_order_expires_after_only_maker_messaged(self):
@@ -1195,7 +1151,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']}, you won the dispute on your order with ID {str(trade.order_id)}.",
+ f"⚖️ Hey {data['maker_nick']}, a dispute has been opened 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)
@@ -1204,7 +1160,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']}, you lost the dispute on your order with ID {str(trade.order_id)}.",
+ f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
)
# def test_dispute_closed_maker_wins(self):
diff --git a/tests/utils/node.py b/tests/utils/node.py
index 002de9bc..d5cb1289 100644
--- a/tests/utils/node.py
+++ b/tests/utils/node.py
@@ -269,7 +269,7 @@ def pay_invoice(node_name, invoice):
node = get_node(node_name)
data = {"payment_request": invoice}
try:
- requests.post(
+ response = requests.post(
f'http://localhost:{node["port"]}/v1/channels/transactions',
json=data,
headers=node["headers"],
@@ -277,6 +277,7 @@ 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
diff --git a/tests/utils/trade.py b/tests/utils/trade.py
index f0258d67..39bcda0a 100644
--- a/tests/utils/trade.py
+++ b/tests/utils/trade.py
@@ -5,11 +5,7 @@ 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_status_notification,
- send_chat_notification,
-)
+from api.tasks import follow_send_payment, send_notification
from tests.utils.node import (
add_invoice,
create_address,
@@ -115,7 +111,7 @@ class Trade:
headers = self.get_robot_auth(robot_index, first_encounter)
self.response = self.client.get(path + params, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def cancel_order(self, robot_index=1):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -123,14 +119,14 @@ class Trade:
body = {"action": "cancel"}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_chat_notification.delay", send_chat_notification)
+ @patch("api.tasks.send_notification.delay", send_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_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def pause_order(self, robot_index=1):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -138,13 +134,13 @@ class Trade:
body = {"action": "pause"}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_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_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def clean_orders(self):
# A background thread checks every 5 second order expirations. We invoke directly during test.
cleaner = CleanOrders()
@@ -159,7 +155,7 @@ class Trade:
generate_blocks(create_address("robot"), 1)
wait_nodes_sync()
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def publish_order(self):
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
self.get_order()
@@ -174,7 +170,7 @@ class Trade:
# Get order
self.get_order()
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def take_order(self):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -182,7 +178,7 @@ class Trade:
body = {"action": "take", "amount": self.take_amount}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def lock_taker_bond(self):
# Takers's first order fetch. Should trigger maker bond hold invoice generation.
self.get_order(self.taker_index)
@@ -197,7 +193,7 @@ class Trade:
# Get order
self.get_order(self.taker_index)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def lock_escrow(self, robot_index):
# Takers's order fetch. Should trigger trade escrow bond hold invoice generation.
self.get_order(robot_index)
@@ -212,7 +208,7 @@ class Trade:
# Get order
self.get_order()
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def submit_payout_address(self, robot_index=1):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -231,7 +227,7 @@ class Trade:
}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def submit_payout_invoice(self, robot_index=1, routing_budget=0):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -253,7 +249,7 @@ class Trade:
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def confirm_fiat(self, robot_index=1):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -261,7 +257,7 @@ class Trade:
body = {"action": "confirm"}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def undo_confirm_sent(self, robot_index=1):
path = reverse("order")
params = f"?order_id={self.order_id}"
@@ -269,14 +265,14 @@ class Trade:
body = {"action": "undo_confirm"}
self.response = self.client.post(path + params, body, **headers)
- @patch("api.tasks.send_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_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_status_notification.delay", send_status_notification)
+ @patch("api.tasks.send_notification.delay", send_notification)
def change_order_status(self, status):
# Change order expiry to now
order = Order.objects.get(id=self.order_id)