Skip to content

Commit 886956c

Browse files
authored
Merge pull request #1356 from RoboSats/new-notifications
New notifications
2 parents e9a49d3 + c960d19 commit 886956c

File tree

12 files changed

+159
-20
lines changed

12 files changed

+159
-20
lines changed

api/admin.py

+2
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ def maker_wins(self, request, queryset):
210210
f"Dispute of order {order.id} solved successfully on favor of the maker",
211211
messages.SUCCESS,
212212
)
213+
send_notification.delay(order_id=order.id, message="dispute_closed")
213214

214215
else:
215216
self.message_user(
@@ -248,6 +249,7 @@ def taker_wins(self, request, queryset):
248249
f"Dispute of order {order.id} solved successfully on favor of the taker",
249250
messages.SUCCESS,
250251
)
252+
send_notification.delay(order_id=order.id, message="dispute_closed")
251253

252254
else:
253255
self.message_user(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.0.6 on 2024-06-29 14:07
2+
3+
import api.models.order
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('api', '0047_notification'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='order',
16+
name='reference',
17+
field=models.UUIDField(default=api.models.order.custom_uuid, editable=False),
18+
),
19+
]

api/models/order.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.db.models.signals import pre_delete
1111
from django.dispatch import receiver
1212
from django.utils import timezone
13+
from api.tasks import send_notification
1314

1415
if config("TESTING", cast=bool, default=False):
1516
import random
@@ -91,10 +92,7 @@ class ExpiryReasons(models.IntegerChoices):
9192
decimal_places=2,
9293
default=0,
9394
null=True,
94-
validators=[
95-
MinValueValidator(Decimal(-100)),
96-
MaxValueValidator(Decimal(999))
97-
],
95+
validators=[MinValueValidator(Decimal(-100)), MaxValueValidator(Decimal(999))],
9896
blank=True,
9997
)
10098
# explicit
@@ -352,6 +350,8 @@ def update_status(self, new_status):
352350
self.log(
353351
f"Order state went from {old_status}: <i>{Order.Status(old_status).label}</i> to {new_status}: <i>{Order.Status(new_status).label}</i>"
354352
)
353+
if new_status == Order.Status.FAI:
354+
send_notification.delay(order_id=self.id, message="lightning_failed")
355355

356356

357357
@receiver(pre_delete, sender=Order)

api/models/robot.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
from pathlib import Path
2-
3-
from django.conf import settings
41
from django.contrib.auth.models import User
52
from django.core.validators import validate_comma_separated_integer_list
63
from django.db import models
7-
from django.db.models.signals import post_save, pre_delete
4+
from django.db.models.signals import post_save
85
from django.dispatch import receiver
9-
from django.utils.html import mark_safe
106

117

128
class Robot(models.Model):

api/notifications.py

+44
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,47 @@ def coordinator_cancelled(self, order):
219219
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."
220220
self.send_message(order, order.maker.robot, title)
221221
return
222+
223+
def dispute_closed(self, order):
224+
lang = order.maker.robot.telegram_lang_code
225+
if order.status == Order.Status.MLD:
226+
# Maker lost dispute
227+
looser = order.maker
228+
winner = order.taker
229+
elif order.status == Order.Status.TLD:
230+
# Taker lost dispute
231+
looser = order.taker
232+
winner = order.maker
233+
234+
lang = looser.robot.telegram_lang_code
235+
if lang == "es":
236+
title = f"⚖️ Hey {looser.username}, has perdido la disputa en la orden con ID {str(order.id)}."
237+
else:
238+
title = f"⚖️ Hey {looser.username}, you lost the dispute on your order with ID {str(order.id)}."
239+
self.send_message(order, looser.robot, title)
240+
241+
lang = winner.robot.telegram_lang_code
242+
if lang == "es":
243+
title = f"⚖️ Hey {winner.username}, has ganado la disputa en la orden con ID {str(order.id)}."
244+
else:
245+
title = f"⚖️ Hey {winner.username}, you won the dispute on your order with ID {str(order.id)}."
246+
self.send_message(order, winner.robot, title)
247+
248+
return
249+
250+
def lightning_failed(self, order):
251+
lang = order.maker.robot.telegram_lang_code
252+
if order.type == Order.Types.BUY:
253+
buyer = order.maker
254+
else:
255+
buyer = order.taker
256+
257+
if lang == "es":
258+
title = f"⚡❌ Hey {buyer.username}, el pago lightning en la order con ID {str(order.id)} ha fallado."
259+
description = "Intentalo de nuevo con una nueva factura o con otra wallet."
260+
else:
261+
title = f"⚡❌ Hey {buyer.username}, the lightning payment on your order with ID {str(order.id)} failed."
262+
description = "Try again with a new invoice or from another wallet."
263+
264+
self.send_message(order, buyer.robot, title, description)
265+
return

api/serializers.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,16 @@ class Meta:
491491

492492

493493
class ListNotificationSerializer(serializers.ModelSerializer):
494+
status = serializers.SerializerMethodField(
495+
help_text="The `status` of the order when the notification was trigered",
496+
)
497+
494498
class Meta:
495499
model = Notification
496-
fields = ("title", "description", "order_id")
500+
fields = ("title", "description", "order_id", "status", "created_at")
501+
502+
def get_status(self, notification) -> int:
503+
return notification.order.status
497504

498505

499506
class OrderPublicSerializer(serializers.ModelSerializer):

api/tasks.py

+6
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,10 @@ def send_notification(order_id=None, chat_message_id=None, message=None):
303303
elif message == "coordinator_cancelled":
304304
notifications.coordinator_cancelled(order)
305305

306+
elif message == "dispute_closed":
307+
notifications.dispute_closed(order)
308+
309+
elif message == "lightning_failed":
310+
notifications.lightning_failed(order)
311+
306312
return

api/views.py

-3
Original file line numberDiff line numberDiff line change
@@ -761,10 +761,7 @@ def get(self, request, format=None):
761761
notification_data = []
762762
for notification in queryset:
763763
data = self.serializer_class(notification).data
764-
data["title"] = str(notification.title)
765-
data["description"] = str(notification.description)
766764
data["order_id"] = notification.order.id
767-
768765
notification_data.append(data)
769766

770767
return Response(notification_data, status=status.HTTP_200_OK)

docker-tests.yml

+7-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ version: '3.9'
1515
services:
1616
bitcoind:
1717
image: ruimarinho/bitcoin-core:${BITCOIND_VERSION:-24.0.1}-alpine
18-
container_name: btc
18+
container_name: test-btc
1919
restart: always
2020
ports:
2121
- "8000:8000"
@@ -50,7 +50,7 @@ services:
5050

5151
coordinator-LND:
5252
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
53-
container_name: coordinator-LND
53+
container_name: test-coordinator-LND
5454
restart: always
5555
volumes:
5656
- bitcoin:/root/.bitcoin/
@@ -83,7 +83,7 @@ services:
8383
coordinator-CLN:
8484
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
8585
restart: always
86-
container_name: coordinator-CLN
86+
container_name: test-coordinator-CLN
8787
environment:
8888
LIGHTNINGD_NETWORK: 'regtest'
8989
volumes:
@@ -97,7 +97,7 @@ services:
9797

9898
robot-LND:
9999
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
100-
container_name: robot-LND
100+
container_name: test-robot-LND
101101
restart: always
102102
volumes:
103103
- bitcoin:/root/.bitcoin/
@@ -129,7 +129,7 @@ services:
129129

130130
redis:
131131
image: redis:${REDIS_VERSION:-7.2.1}-alpine
132-
container_name: redis
132+
container_name: test-redis
133133
restart: always
134134
volumes:
135135
- redisdata:/data
@@ -141,7 +141,7 @@ services:
141141
args:
142142
DEVELOPMENT: True
143143
image: backend-image
144-
container_name: coordinator
144+
container_name: test-coordinator
145145
restart: always
146146
environment:
147147
DEVELOPMENT: True
@@ -171,7 +171,7 @@ services:
171171

172172
postgres:
173173
image: postgres:${POSTGRES_VERSION:-14.2}-alpine
174-
container_name: sql
174+
container_name: test-sql
175175
restart: always
176176
environment:
177177
POSTGRES_PASSWORD: 'example'

docs/assets/schemas/api-latest.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -1106,8 +1106,16 @@ components:
11061106
order_id:
11071107
type: integer
11081108
readOnly: true
1109+
status:
1110+
type: integer
1111+
readOnly: true
1112+
description: The `status` of the order when the notification was trigered
1113+
created_at:
1114+
type: string
1115+
format: date-time
11091116
required:
11101117
- order_id
1118+
- status
11111119
ListOrder:
11121120
type: object
11131121
properties:

tests/test_trade_pipeline.py

+54
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,60 @@ def test_order_expires_after_only_maker_messaged(self):
11631163
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
11641164
)
11651165

1166+
# def test_dispute_closed_maker_wins(self):
1167+
# trade = Trade(self.client)
1168+
# trade.publish_order()
1169+
# trade.take_order()
1170+
# trade.lock_taker_bond()
1171+
# trade.lock_escrow(trade.taker_index)
1172+
# trade.submit_payout_invoice(trade.maker_index)
1173+
1174+
# # Admin resolves dispute
1175+
1176+
# trade.clean_orders()
1177+
1178+
# maker_headers = trade.get_robot_auth(trade.maker_index)
1179+
# response = self.client.get(reverse("notifications"), **maker_headers)
1180+
# self.assertResponse(response)
1181+
# notifications_data = list(response.json())
1182+
# self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
1183+
# self.assertEqual(
1184+
# notifications_data[0]["title"],
1185+
# f"⚖️ Hey {data['maker_nick']}, you won the dispute on your order with ID {str(trade.order_id)}."
1186+
# )
1187+
# taker_headers = trade.get_robot_auth(trade.taker_index)
1188+
# response = self.client.get(reverse("notifications"), **taker_headers)
1189+
# self.assertResponse(response)
1190+
# notifications_data = list(response.json())
1191+
# self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
1192+
# self.assertEqual(
1193+
# notifications_data[0]["title"],
1194+
# f"⚖️ Hey {data['taker_nick']}, you lost the dispute on your order with ID {str(trade.order_id)}."
1195+
# )
1196+
1197+
def test_lightning_payment_failed(self):
1198+
trade = Trade(self.client)
1199+
trade.publish_order()
1200+
trade.take_order()
1201+
trade.lock_taker_bond()
1202+
trade.lock_escrow(trade.taker_index)
1203+
trade.submit_payout_invoice(trade.maker_index)
1204+
1205+
trade.change_order_status(Order.Status.FAI)
1206+
1207+
trade.clean_orders()
1208+
1209+
maker_headers = trade.get_robot_auth(trade.maker_index)
1210+
maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname")
1211+
response = self.client.get(reverse("notifications"), **maker_headers)
1212+
self.assertResponse(response)
1213+
notifications_data = list(response.json())
1214+
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
1215+
self.assertEqual(
1216+
notifications_data[0]["title"],
1217+
f"⚡❌ Hey {maker_nick}, the lightning payment on your order with ID {str(trade.order_id)} failed.",
1218+
)
1219+
11661220
def test_withdraw_reward_after_unilateral_cancel(self):
11671221
"""
11681222
Tests withdraw rewards as taker after maker cancels order unilaterally

tests/utils/trade.py

+6
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,9 @@ def expire_order(self):
271271
order = Order.objects.get(id=self.order_id)
272272
order.expires_at = datetime.now()
273273
order.save()
274+
275+
@patch("api.tasks.send_notification.delay", send_notification)
276+
def change_order_status(self, status):
277+
# Change order expiry to now
278+
order = Order.objects.get(id=self.order_id)
279+
order.update_status(status)

0 commit comments

Comments
 (0)