Skip to content

New notifications #1356

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def maker_wins(self, request, queryset):
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(
Expand Down Expand Up @@ -248,6 +249,7 @@ def taker_wins(self, request, queryset):
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(
Expand Down
19 changes: 19 additions & 0 deletions api/migrations/0048_alter_order_reference.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
8 changes: 4 additions & 4 deletions api/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils import timezone
from api.tasks import send_notification

if config("TESTING", cast=bool, default=False):
import random
Expand Down Expand Up @@ -91,10 +92,7 @@ class ExpiryReasons(models.IntegerChoices):
decimal_places=2,
default=0,
null=True,
validators=[
MinValueValidator(Decimal(-100)),
MaxValueValidator(Decimal(999))
],
validators=[MinValueValidator(Decimal(-100)), MaxValueValidator(Decimal(999))],
blank=True,
)
# explicit
Expand Down Expand Up @@ -352,6 +350,8 @@ def update_status(self, new_status):
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")


@receiver(pre_delete, sender=Order)
Expand Down
6 changes: 1 addition & 5 deletions api/models/robot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
from pathlib import Path

from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import validate_comma_separated_integer_list
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.utils.html import mark_safe


class Robot(models.Model):
Expand Down
44 changes: 44 additions & 0 deletions api/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,47 @@ 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:
# 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
9 changes: 8 additions & 1 deletion api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,16 @@ class Meta:


class ListNotificationSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField(
help_text="The `status` of the order when the notification was trigered",
)

class Meta:
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):
Expand Down
6 changes: 6 additions & 0 deletions api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,10 @@ def send_notification(order_id=None, chat_message_id=None, message=None):
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
3 changes: 0 additions & 3 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,10 +761,7 @@ def get(self, request, format=None):
notification_data = []
for notification in queryset:
data = self.serializer_class(notification).data
data["title"] = str(notification.title)
data["description"] = str(notification.description)
data["order_id"] = notification.order.id

notification_data.append(data)

return Response(notification_data, status=status.HTTP_200_OK)
Expand Down
14 changes: 7 additions & 7 deletions docker-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ version: '3.9'
services:
bitcoind:
image: ruimarinho/bitcoin-core:${BITCOIND_VERSION:-24.0.1}-alpine
container_name: btc
container_name: test-btc
restart: always
ports:
- "8000:8000"
Expand Down Expand Up @@ -50,7 +50,7 @@ services:

coordinator-LND:
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
container_name: coordinator-LND
container_name: test-coordinator-LND
restart: always
volumes:
- bitcoin:/root/.bitcoin/
Expand Down Expand Up @@ -83,7 +83,7 @@ services:
coordinator-CLN:
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
restart: always
container_name: coordinator-CLN
container_name: test-coordinator-CLN
environment:
LIGHTNINGD_NETWORK: 'regtest'
volumes:
Expand All @@ -97,7 +97,7 @@ services:

robot-LND:
image: lightninglabs/lnd:${LND_VERSION:-v0.17.0-beta}
container_name: robot-LND
container_name: test-robot-LND
restart: always
volumes:
- bitcoin:/root/.bitcoin/
Expand Down Expand Up @@ -129,7 +129,7 @@ services:

redis:
image: redis:${REDIS_VERSION:-7.2.1}-alpine
container_name: redis
container_name: test-redis
restart: always
volumes:
- redisdata:/data
Expand All @@ -141,7 +141,7 @@ services:
args:
DEVELOPMENT: True
image: backend-image
container_name: coordinator
container_name: test-coordinator
restart: always
environment:
DEVELOPMENT: True
Expand Down Expand Up @@ -171,7 +171,7 @@ services:

postgres:
image: postgres:${POSTGRES_VERSION:-14.2}-alpine
container_name: sql
container_name: test-sql
restart: always
environment:
POSTGRES_PASSWORD: 'example'
Expand Down
8 changes: 8 additions & 0 deletions docs/assets/schemas/api-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1106,8 +1106,16 @@ components:
order_id:
type: integer
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:
- order_id
- status
ListOrder:
type: object
properties:
Expand Down
54 changes: 54 additions & 0 deletions tests/test_trade_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,60 @@ def test_order_expires_after_only_maker_messaged(self):
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)}."
# )
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the dispute resolution is an admin action, I'm not quite sure how to do it, ideas @Reckless-Satoshi ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we could directly call

def maker_wins(self, request, queryset):
... Looks difficult. Maybe we should explore how the request sent from the admin panel looks when solving a dispute and try to imitate it here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And fake the admin request with credentials? I'll take a look but for this kind of solutions they are always tricky


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):
"""
Tests withdraw rewards as taker after maker cancels order unilaterally
Expand Down
6 changes: 6 additions & 0 deletions tests/utils/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,9 @@ def expire_order(self):
order = Order.objects.get(id=self.order_id)
order.expires_at = datetime.now()
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