mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-14 03:16:24 +00:00
Merge pull request #1356 from RoboSats/new-notifications
New notifications
This commit is contained in:
commit
886956c5a9
@ -210,6 +210,7 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
|||||||
f"Dispute of order {order.id} solved successfully on favor of the maker",
|
f"Dispute of order {order.id} solved successfully on favor of the maker",
|
||||||
messages.SUCCESS,
|
messages.SUCCESS,
|
||||||
)
|
)
|
||||||
|
send_notification.delay(order_id=order.id, message="dispute_closed")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
@ -248,6 +249,7 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
|||||||
f"Dispute of order {order.id} solved successfully on favor of the taker",
|
f"Dispute of order {order.id} solved successfully on favor of the taker",
|
||||||
messages.SUCCESS,
|
messages.SUCCESS,
|
||||||
)
|
)
|
||||||
|
send_notification.delay(order_id=order.id, message="dispute_closed")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
|
19
api/migrations/0048_alter_order_reference.py
Normal file
19
api/migrations/0048_alter_order_reference.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-06-29 14:07
|
||||||
|
|
||||||
|
import api.models.order
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0047_notification'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='reference',
|
||||||
|
field=models.UUIDField(default=api.models.order.custom_uuid, editable=False),
|
||||||
|
),
|
||||||
|
]
|
@ -10,6 +10,7 @@ from django.db import models
|
|||||||
from django.db.models.signals import pre_delete
|
from django.db.models.signals import pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from api.tasks import send_notification
|
||||||
|
|
||||||
if config("TESTING", cast=bool, default=False):
|
if config("TESTING", cast=bool, default=False):
|
||||||
import random
|
import random
|
||||||
@ -91,10 +92,7 @@ class Order(models.Model):
|
|||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=0,
|
default=0,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[
|
validators=[MinValueValidator(Decimal(-100)), MaxValueValidator(Decimal(999))],
|
||||||
MinValueValidator(Decimal(-100)),
|
|
||||||
MaxValueValidator(Decimal(999))
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
# explicit
|
# explicit
|
||||||
@ -352,6 +350,8 @@ class Order(models.Model):
|
|||||||
self.log(
|
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>"
|
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")
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Order)
|
@receiver(pre_delete, sender=Order)
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.validators import validate_comma_separated_integer_list
|
from django.core.validators import validate_comma_separated_integer_list
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_save, pre_delete
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.html import mark_safe
|
|
||||||
|
|
||||||
|
|
||||||
class Robot(models.Model):
|
class Robot(models.Model):
|
||||||
|
@ -219,3 +219,47 @@ class Notifications:
|
|||||||
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."
|
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)
|
self.send_message(order, order.maker.robot, title)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def dispute_closed(self, order):
|
||||||
|
lang = order.maker.robot.telegram_lang_code
|
||||||
|
if order.status == Order.Status.MLD:
|
||||||
|
# Maker lost dispute
|
||||||
|
looser = order.maker
|
||||||
|
winner = order.taker
|
||||||
|
elif order.status == Order.Status.TLD:
|
||||||
|
# Taker lost dispute
|
||||||
|
looser = order.taker
|
||||||
|
winner = order.maker
|
||||||
|
|
||||||
|
lang = looser.robot.telegram_lang_code
|
||||||
|
if lang == "es":
|
||||||
|
title = f"⚖️ Hey {looser.username}, has perdido la disputa en la orden con ID {str(order.id)}."
|
||||||
|
else:
|
||||||
|
title = f"⚖️ Hey {looser.username}, you lost the dispute on your order with ID {str(order.id)}."
|
||||||
|
self.send_message(order, looser.robot, title)
|
||||||
|
|
||||||
|
lang = winner.robot.telegram_lang_code
|
||||||
|
if lang == "es":
|
||||||
|
title = f"⚖️ Hey {winner.username}, has ganado la disputa en la orden con ID {str(order.id)}."
|
||||||
|
else:
|
||||||
|
title = f"⚖️ Hey {winner.username}, you won the dispute on your order with ID {str(order.id)}."
|
||||||
|
self.send_message(order, winner.robot, title)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def lightning_failed(self, order):
|
||||||
|
lang = order.maker.robot.telegram_lang_code
|
||||||
|
if order.type == Order.Types.BUY:
|
||||||
|
buyer = order.maker
|
||||||
|
else:
|
||||||
|
buyer = order.taker
|
||||||
|
|
||||||
|
if lang == "es":
|
||||||
|
title = f"⚡❌ Hey {buyer.username}, el pago lightning en la order con ID {str(order.id)} ha fallado."
|
||||||
|
description = "Intentalo de nuevo con una nueva factura o con otra wallet."
|
||||||
|
else:
|
||||||
|
title = f"⚡❌ Hey {buyer.username}, the lightning payment on your order with ID {str(order.id)} failed."
|
||||||
|
description = "Try again with a new invoice or from another wallet."
|
||||||
|
|
||||||
|
self.send_message(order, buyer.robot, title, description)
|
||||||
|
return
|
||||||
|
@ -491,9 +491,16 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ListNotificationSerializer(serializers.ModelSerializer):
|
class ListNotificationSerializer(serializers.ModelSerializer):
|
||||||
|
status = serializers.SerializerMethodField(
|
||||||
|
help_text="The `status` of the order when the notification was trigered",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Notification
|
model = Notification
|
||||||
fields = ("title", "description", "order_id")
|
fields = ("title", "description", "order_id", "status", "created_at")
|
||||||
|
|
||||||
|
def get_status(self, notification) -> int:
|
||||||
|
return notification.order.status
|
||||||
|
|
||||||
|
|
||||||
class OrderPublicSerializer(serializers.ModelSerializer):
|
class OrderPublicSerializer(serializers.ModelSerializer):
|
||||||
|
@ -303,4 +303,10 @@ def send_notification(order_id=None, chat_message_id=None, message=None):
|
|||||||
elif message == "coordinator_cancelled":
|
elif message == "coordinator_cancelled":
|
||||||
notifications.coordinator_cancelled(order)
|
notifications.coordinator_cancelled(order)
|
||||||
|
|
||||||
|
elif message == "dispute_closed":
|
||||||
|
notifications.dispute_closed(order)
|
||||||
|
|
||||||
|
elif message == "lightning_failed":
|
||||||
|
notifications.lightning_failed(order)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -761,10 +761,7 @@ class NotificationsView(ListAPIView):
|
|||||||
notification_data = []
|
notification_data = []
|
||||||
for notification in queryset:
|
for notification in queryset:
|
||||||
data = self.serializer_class(notification).data
|
data = self.serializer_class(notification).data
|
||||||
data["title"] = str(notification.title)
|
|
||||||
data["description"] = str(notification.description)
|
|
||||||
data["order_id"] = notification.order.id
|
data["order_id"] = notification.order.id
|
||||||
|
|
||||||
notification_data.append(data)
|
notification_data.append(data)
|
||||||
|
|
||||||
return Response(notification_data, status=status.HTTP_200_OK)
|
return Response(notification_data, status=status.HTTP_200_OK)
|
||||||
|
@ -15,7 +15,7 @@ version: '3.9'
|
|||||||
services:
|
services:
|
||||||
bitcoind:
|
bitcoind:
|
||||||
image: ruimarinho/bitcoin-core:${BITCOIND_VERSION:-24.0.1}-alpine
|
image: ruimarinho/bitcoin-core:${BITCOIND_VERSION:-24.0.1}-alpine
|
||||||
container_name: btc
|
container_name: test-btc
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
@ -50,7 +50,7 @@ services:
|
|||||||
|
|
||||||
coordinator-LND:
|
coordinator-LND:
|
||||||
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
|
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
|
||||||
container_name: coordinator-LND
|
container_name: test-coordinator-LND
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- bitcoin:/root/.bitcoin/
|
- bitcoin:/root/.bitcoin/
|
||||||
@ -83,7 +83,7 @@ services:
|
|||||||
coordinator-CLN:
|
coordinator-CLN:
|
||||||
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
|
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
|
||||||
restart: always
|
restart: always
|
||||||
container_name: coordinator-CLN
|
container_name: test-coordinator-CLN
|
||||||
environment:
|
environment:
|
||||||
LIGHTNINGD_NETWORK: 'regtest'
|
LIGHTNINGD_NETWORK: 'regtest'
|
||||||
volumes:
|
volumes:
|
||||||
@ -97,7 +97,7 @@ services:
|
|||||||
|
|
||||||
robot-LND:
|
robot-LND:
|
||||||
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
|
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
|
||||||
container_name: robot-LND
|
container_name: test-robot-LND
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- bitcoin:/root/.bitcoin/
|
- bitcoin:/root/.bitcoin/
|
||||||
@ -129,7 +129,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:${REDIS_VERSION:-7.2.1}-alpine
|
image: redis:${REDIS_VERSION:-7.2.1}-alpine
|
||||||
container_name: redis
|
container_name: test-redis
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- redisdata:/data
|
- redisdata:/data
|
||||||
@ -141,7 +141,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
DEVELOPMENT: True
|
DEVELOPMENT: True
|
||||||
image: backend-image
|
image: backend-image
|
||||||
container_name: coordinator
|
container_name: test-coordinator
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
DEVELOPMENT: True
|
DEVELOPMENT: True
|
||||||
@ -171,7 +171,7 @@ services:
|
|||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:${POSTGRES_VERSION:-14.2}-alpine
|
image: postgres:${POSTGRES_VERSION:-14.2}-alpine
|
||||||
container_name: sql
|
container_name: test-sql
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: 'example'
|
POSTGRES_PASSWORD: 'example'
|
||||||
|
@ -1106,8 +1106,16 @@ components:
|
|||||||
order_id:
|
order_id:
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
description: The `status` of the order when the notification was trigered
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
required:
|
required:
|
||||||
- order_id
|
- order_id
|
||||||
|
- status
|
||||||
ListOrder:
|
ListOrder:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1163,6 +1163,60 @@ class TradeTest(BaseAPITestCase):
|
|||||||
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened 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):
|
||||||
|
# trade = Trade(self.client)
|
||||||
|
# trade.publish_order()
|
||||||
|
# trade.take_order()
|
||||||
|
# trade.lock_taker_bond()
|
||||||
|
# trade.lock_escrow(trade.taker_index)
|
||||||
|
# trade.submit_payout_invoice(trade.maker_index)
|
||||||
|
|
||||||
|
# # Admin resolves dispute
|
||||||
|
|
||||||
|
# trade.clean_orders()
|
||||||
|
|
||||||
|
# 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']}, 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)
|
||||||
|
# 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['taker_nick']}, you lost the dispute on your order with ID {str(trade.order_id)}."
|
||||||
|
# )
|
||||||
|
|
||||||
|
def test_lightning_payment_failed(self):
|
||||||
|
trade = Trade(self.client)
|
||||||
|
trade.publish_order()
|
||||||
|
trade.take_order()
|
||||||
|
trade.lock_taker_bond()
|
||||||
|
trade.lock_escrow(trade.taker_index)
|
||||||
|
trade.submit_payout_invoice(trade.maker_index)
|
||||||
|
|
||||||
|
trade.change_order_status(Order.Status.FAI)
|
||||||
|
|
||||||
|
trade.clean_orders()
|
||||||
|
|
||||||
|
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)
|
||||||
|
notifications_data = list(response.json())
|
||||||
|
self.assertEqual(notifications_data[0]["order_id"], trade.order_id)
|
||||||
|
self.assertEqual(
|
||||||
|
notifications_data[0]["title"],
|
||||||
|
f"⚡❌ Hey {maker_nick}, the lightning payment on your order with ID {str(trade.order_id)} failed.",
|
||||||
|
)
|
||||||
|
|
||||||
def test_withdraw_reward_after_unilateral_cancel(self):
|
def test_withdraw_reward_after_unilateral_cancel(self):
|
||||||
"""
|
"""
|
||||||
Tests withdraw rewards as taker after maker cancels order unilaterally
|
Tests withdraw rewards as taker after maker cancels order unilaterally
|
||||||
|
@ -271,3 +271,9 @@ class Trade:
|
|||||||
order = Order.objects.get(id=self.order_id)
|
order = Order.objects.get(id=self.order_id)
|
||||||
order.expires_at = datetime.now()
|
order.expires_at = datetime.now()
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
|
@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)
|
||||||
|
order.update_status(status)
|
||||||
|
Loading…
Reference in New Issue
Block a user