Add Chat endpoint to API v0 (#288)

* Add /api/chat route and GET method

* Add message POST method

* Wrap /api/chat GET in /api/order GET

* Add send channel message on POST request

* Fix OAS schema bug
This commit is contained in:
Reckless_Satoshi 2022-10-16 21:11:48 +00:00 committed by GitHub
parent f3d36fb95e
commit 79dad7afe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 237 additions and 9 deletions

View File

@ -448,6 +448,8 @@ class OrderPublicSerializer(serializers.ModelSerializer):
"maker_status",
"price",
"escrow_duration",
"satoshis_now",
"bond_size"
)

View File

@ -1,6 +1,7 @@
from django.urls import path
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView, StealthView
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from chat.views import ChatView
urlpatterns = [
path('schema/', SpectacularAPIView.as_view(), name='schema'),
@ -20,4 +21,5 @@ urlpatterns = [
path("historical/", HistoricalView.as_view()),
path("ticks/", TickView.as_view()),
path("stealth/", StealthView.as_view()),
path("chat/", ChatView.as_view({"get": "get","post":"post"})),
]

View File

@ -8,14 +8,12 @@ from rest_framework.exceptions import bad_request
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView
from rest_framework.views import APIView
from rest_framework.response import Response
import textwrap
from django.contrib.auth import authenticate, login, logout
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
from api.oas_schemas import BookViewSchema, HistoricalViewSchema, InfoViewSchema, LimitViewSchema, MakerViewSchema, OrderViewSchema, PriceViewSchema, RewardViewSchema, StealthViewSchema, TickViewSchema, UserViewSchema
from chat.views import ChatView
from api.serializers import InfoSerializer, ListOrderSerializer, MakeOrderSerializer, OrderPublicSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer, TickSerializer, StealthSerializer
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
from control.models import AccountingDay, BalanceLog
@ -28,7 +26,6 @@ from .nick_generator.nick_generator import NickGenerator
from robohash import Robohash
from scipy.stats import entropy
from math import log2
import numpy as np
import hashlib
from pathlib import Path
from datetime import timedelta, datetime
@ -370,6 +367,10 @@ class OrderView(viewsets.ViewSet):
data["asked_for_cancel"] = True
else:
data["asked_for_cancel"] = False
offset = request.GET.get('offset', None)
if offset:
data["chat"] = ChatView.get(None, request).data
# 9) If status is 'DIS' and all HTLCS are in LOCKED
elif order.status == Order.Status.DIS:

View File

@ -2,6 +2,7 @@ from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from api.models import Order
from chat.models import ChatRoom, Message
from asgiref.sync import async_to_sync
import json

26
chat/serializers.py Normal file
View File

@ -0,0 +1,26 @@
from rest_framework import serializers
from chat.models import Message
class ChatSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = (
"index",
"sender",
"PGP_message",
"created_at",
)
depth = 0
class PostMessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = ("PGP_message","order","offset")
depth = 0
offset = serializers.IntegerField(allow_null=True,
default=None,
required=False,
min_value=0,
help_text="Offset for message index to get as response")

View File

@ -1,6 +1,202 @@
from django.shortcuts import render
from operator import index
from rest_framework import status, viewsets
from chat.serializers import ChatSerializer, PostMessageSerializer
from chat.models import Message, ChatRoom
from api.models import Order, User
from rest_framework.response import Response
from datetime import timedelta
from django.utils import timezone
# def room(request, order_id):
# return render(request, 'chatroom.html', {
# 'order_id': order_id
# })
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
class ChatView(viewsets.ViewSet):
serializer_class = PostMessageSerializer
lookup_url_kwarg = ["order_id","offset"]
queryset = Message.objects.filter(order__status__in=[Order.Status.CHA, Order.Status.FSE])
def get(self, request, format=None):
"""
Returns chat messages for an order with an index higher than `offset`.
"""
order_id = request.GET.get("order_id", None)
offset = request.GET.get("offset", 0)
if order_id is None:
return Response(
{
"bad_request":
"Order ID does not exist"
},
status.HTTP_400_BAD_REQUEST,
)
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
{
"bad_request":
"You are not participant in this order"
},
status.HTTP_400_BAD_REQUEST,
)
if not order.status in [Order.Status.CHA, Order.Status.FSE]:
return Response(
{
"bad_request":
"Order is not in chat status"
},
status.HTTP_400_BAD_REQUEST,
)
queryset = Message.objects.filter(order=order, index__gt=offset)
chatroom = ChatRoom.objects.get(order=order)
# Poor idea: is_peer_connected() mockup. Update connection status based on last time a GET request was sent
if chatroom.maker == request.user:
chatroom.taker_connected = order.taker_last_seen > (timezone.now() - timedelta(minutes=1))
chatroom.maker_connected = True
chatroom.save()
peer_connected = chatroom.taker_connected
elif chatroom.taker == request.user:
chatroom.maker_connected = order.maker_last_seen > (timezone.now() - timedelta(minutes=1))
chatroom.taker_connected = True
chatroom.save()
peer_connected = chatroom.maker_connected
messages = []
for message in queryset:
d = ChatSerializer(message).data
print(d)
# Re-serialize so the response is identical to the consumer message
data = {
'index':d['index'],
'time':d['created_at'],
'message':d['PGP_message'],
'nick': User.objects.get(id=d['sender']).username
}
messages.append(data)
response = {'peer_connected': peer_connected, 'messages':messages}
return Response(response, status.HTTP_200_OK)
def post(self, request, format=None):
"""
Adds one new message to the chatroom.
"""
serializer = self.serializer_class(data=request.data)
# Return bad request if serializer is not valid
if not serializer.is_valid():
context = {"bad_request": "Invalid serializer"}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
print(request)
order_id = serializer.data.get("order", None)
if order_id is None:
return Response(
{
"bad_request":
"Order ID does not exist"
},
status.HTTP_400_BAD_REQUEST,
)
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
{
"bad_request":
"You are not participant in this order"
},
status.HTTP_400_BAD_REQUEST,
)
if not order.status in [Order.Status.CHA, Order.Status.FSE]:
return Response(
{
"bad_request":
"Order is not in chat status"
},
status.HTTP_400_BAD_REQUEST,
)
if order.maker == request.user:
sender = order.maker
receiver = order.taker
elif order.taker == request.user:
sender = order.taker
receiver = order.maker
chatroom, _ = ChatRoom.objects.get_or_create(
id=order_id,
order=order,
room_group_name=f"chat_order_{order_id}",
defaults={
"maker": order.maker,
"maker_connected": order.maker == request.user,
"taker": order.taker,
"taker_connected": order.taker == request.user,
}
)
last_index = Message.objects.filter(order=order, chatroom=chatroom).count()
new_message = Message.objects.create(
index=last_index+1,
PGP_message=serializer.data.get("PGP_message"),
order=order,
chatroom=chatroom,
sender=sender,
receiver=receiver,
)
# Send websocket message
if chatroom.maker == request.user:
peer_connected = chatroom.taker_connected
elif chatroom.taker == request.user:
peer_connected = chatroom.maker_connected
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f"chat_order_{order_id}",
{
"type": "PGP_message",
"index": new_message.index,
"message": new_message.PGP_message,
"time": str(new_message.created_at),
"nick": new_message.sender.username,
"peer_connected": peer_connected,
}
)
# if offset is given, reply with messages
offset = serializer.data.get("offset", None)
if offset:
queryset = Message.objects.filter(order=order, index__gt=offset)
messages = []
for message in queryset:
d = ChatSerializer(message).data
print(d)
# Re-serialize so the response is identical to the consumer message
data = {
'index':d['index'],
'time':d['created_at'],
'message':d['PGP_message'],
'nick': User.objects.get(id=d['sender']).username
}
messages.append(data)
response = {'peer_connected': peer_connected, 'messages':messages}
else:
response = {}
return Response(response, status.HTTP_200_OK)