Work on update order endpoint and taker requests

This commit is contained in:
Reckless_Satoshi 2022-01-04 16:13:08 -08:00
parent 4d9a5023e0
commit 9ade961e0f
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
10 changed files with 177 additions and 50 deletions

View File

@ -24,8 +24,8 @@ class EUserAdmin(UserAdmin):
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id','type','maker','taker','status','amount','currency','created_at','expires_at')
list_display_links = ('maker','taker')
list_display = ('id','type','maker','taker','status','amount','currency','created_at','expires_at', 'invoice')
list_display_links = ['id']
pass
@admin.register(Profile)

View File

@ -44,11 +44,11 @@ class Order(models.Model):
UPI = 15, 'Updated invoice'
DIS = 16, 'In dispute'
MLD = 17, 'Maker lost dispute'
TLD = 18, 'Taker lost dispute'
EXP = 19, 'Expired'
# TLD = 18, 'Taker lost dispute'
# EXP = 19, 'Expired'
# order info, id = models.CharField(max_length=64, unique=True, null=False)
status = models.PositiveSmallIntegerField(choices=Status.choices, default=Status.WFB)
# order info
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=int(Status.WFB))
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
@ -79,6 +79,7 @@ class Order(models.Model):
invoice = models.CharField(max_length=300, unique=False, null=True, default=None)
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
# Ratings stored as a comma separated integer list
@ -91,7 +92,7 @@ class Profile(models.Model):
lost_disputes = models.PositiveIntegerField(null=False, default=0)
# RoboHash
avatar = models.ImageField(default="static/assets/avatars/unknown.png", verbose_name='Avatar')
avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar')
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
@ -102,13 +103,18 @@ class Profile(models.Model):
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
@receiver(pre_delete, sender=User)
def del_avatar_from_disk(sender, instance, **kwargs):
avatar_file=Path('frontend/' + instance.profile.avatar.url)
avatar_file.unlink() # FIX deleting user fails if avatar is not found
def __str__(self):
return self.user.username
# to display avatars in admin panel
def get_avatar(self):
if not self.avatar:
return 'static/assets/avatars/unknown.png'
return 'static/assets/misc/unknown_avatar.png'
return self.avatar.url
# method to create a fake table field in read only mode

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from .models import Order
class OrderSerializer(serializers.ModelSerializer):
class ListOrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id','status','created_at','expires_at','type','currency','amount','payment_method','is_explicit','premium','satoshis','maker','taker')
@ -9,4 +9,9 @@ class OrderSerializer(serializers.ModelSerializer):
class MakeOrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('type','currency','amount','payment_method','is_explicit','premium','satoshis')
fields = ('type','currency','amount','payment_method','is_explicit','premium','satoshis')
class UpdateOrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id','invoice')

View File

@ -1,9 +1,9 @@
from django.urls import path
from .views import MakeOrder, OrderView, UserGenerator, BookView
from .views import OrderMakerView, OrderView, UserView, BookView
urlpatterns = [
path('make/', MakeOrder.as_view()),
path('order/', OrderView.as_view()),
path('usergen/', UserGenerator.as_view()),
path('make/', OrderMakerView.as_view()),
path('order/', OrderView.as_view({'get':'get','post':'take_or_update'})),
path('usergen/', UserView.as_view()),
path('book/', BookView.as_view()),
]

View File

@ -1,11 +1,13 @@
from rest_framework import serializers, status
from rest_framework import status
from rest_framework.generics import CreateAPIView, ListAPIView
from rest_framework.views import APIView
from rest_framework import viewsets
from rest_framework.response import Response
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.conf.urls.static import static
from .serializers import OrderSerializer, MakeOrderSerializer
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
from .models import Order
from .nick_generator.nick_generator import NickGenerator
@ -24,9 +26,27 @@ expiration_time = 8
avatar_path = Path('frontend/static/assets/avatars')
avatar_path.mkdir(parents=True, exist_ok=True)
def validate_already_maker_or_taker(request):
'''Checks if the user is already partipant of an order'''
queryset = Order.objects.filter(maker=request.user.id)
if queryset.exists():
return False, Response({'Bad Request':'You are already maker of an order'}, status=status.HTTP_400_BAD_REQUEST)
queryset = Order.objects.filter(taker=request.user.id)
if queryset.exists():
return False, Response({'Bad Request':'You are already taker of an order'}, status=status.HTTP_400_BAD_REQUEST)
return True, None
def validate_ln_invoice(invoice):
'''Checks if a LN invoice is valid'''
#TODO
return True
# Create your views here.
class MakeOrder(APIView):
class OrderMakerView(CreateAPIView):
serializer_class = MakeOrderSerializer
def post(self,request):
@ -41,17 +61,14 @@ class MakeOrder(APIView):
satoshis = serializer.data.get('satoshis')
is_explicit = serializer.data.get('is_explicit')
# query if the user is already a maker or taker, return error
queryset = Order.objects.filter(maker=request.user.id)
if queryset.exists():
return Response({'Bad Request':'You are already maker of an order'},status=status.HTTP_400_BAD_REQUEST)
queryset = Order.objects.filter(taker=request.user.id)
if queryset.exists():
return Response({'Bad Request':'You are already taker of an order'},status=status.HTTP_400_BAD_REQUEST)
valid, response = validate_already_maker_or_taker(request)
if not valid:
return response
# Creates a new order in db
order = Order(
type=otype,
status=int(Order.Status.PUB), # TODO orders are public by default for the moment. Future it will be WFB (waiting for bond)
currency=currency,
amount=amount,
payment_method=payment_method,
@ -65,11 +82,11 @@ class MakeOrder(APIView):
if not serializer.is_valid():
return Response(status=status.HTTP_400_BAD_REQUEST)
return Response(OrderSerializer(order).data, status=status.HTTP_201_CREATED)
return Response(ListOrderSerializer(order).data, status=status.HTTP_201_CREATED)
class OrderView(APIView):
serializer_class = OrderSerializer
class OrderView(viewsets.ViewSet):
serializer_class = UpdateOrderSerializer
lookup_url_kwarg = 'order_id'
def get(self, request, format=None):
@ -81,15 +98,14 @@ class OrderView(APIView):
# check if exactly one order is found in the db
if len(order) == 1 :
order = order[0]
data = self.serializer_class(order).data
data = ListOrderSerializer(order).data
nickname = request.user.username
# Check if requester is participant in the order and add boolean to response
data['is_participant'] = (str(order.maker) == nickname or str(order.taker) == nickname)
#To do fix: data['status_message'] = Order.Status.get(order.status).label
data['status_message'] = Order.Status.WFB.label # Hardcoded WFB, should use order.status value.
# Check if requester is participant in the order and add boolean to response
data['is_participant'] = (str(order.maker) == nickname or str(order.taker) == nickname)
data['maker_nick'] = str(order.maker)
data['taker_nick'] = str(order.taker)
@ -105,7 +121,48 @@ class OrderView(APIView):
return Response({'Bad Request':'Order ID parameter not found in request'}, status=status.HTTP_400_BAD_REQUEST)
class UserGenerator(APIView):
def take_or_update(self, request, format=None):
order_id = request.GET.get(self.lookup_url_kwarg)
serializer = UpdateOrderSerializer(data=request.data)
order = Order.objects.get(id=order_id)
if serializer.is_valid():
invoice = serializer.data.get('invoice')
# If this is an empty POST request (no invoice), it must be taker request!
if not invoice and order.status == int(Order.Status.PUB):
valid, response = validate_already_maker_or_taker(request)
if not valid:
return response
order.taker = self.request.user
order.status = int(Order.Status.TAK)
data = ListOrderSerializer(order).data
# An invoice came in! update it
elif invoice:
if validate_ln_invoice(invoice):
order.invoice = invoice
#TODO Validate if request comes from PARTICIPANT AND BUYER
#If the order status was Payment Failed. Move foward to invoice Updated.
if order.status == int(Order.Status.FAI):
order.status = int(Order.Status.UPI)
else:
return Response({'bad_request':'Invalid Lightning Network Invoice. It starts by LNTB...'})
# Something else is going on. Probably not allowed.
else:
return Response({'bad_request':'Not allowed'})
order.save()
return self.get(request)
class UserView(APIView):
lookup_url_kwarg = 'token'
NickGen = NickGenerator(
lang='English',
@ -114,6 +171,7 @@ class UserGenerator(APIView):
use_noun=True,
max_num=999)
# Probably should be turned into a post method
def get(self,request, format=None):
'''
Get a new user derived from a high entropy token
@ -181,40 +239,39 @@ class UserGenerator(APIView):
def delete(self,request):
user = User.objects.get(id = request.user.id)
# TO DO. Pressing give me another will delete the logged in user
# TO DO. Pressing "give me another" deletes the logged in user
# However it might be a long time recovered user
# Only delete if user live is < 5 minutes
# TODO check if user exists AND it is not a maker or taker!
if user is not None:
avatar_file = avatar_path.joinpath(str(request.user)+".png")
avatar_file.unlink() # Unsafe if avatar does not exist.
logout(request)
user.delete()
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_301_MOVED_PERMANENTLY)
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_302_FOUND)
return Response(status=status.HTTP_403_FORBIDDEN)
class BookView(APIView):
serializer_class = OrderSerializer
class BookView(ListAPIView):
serializer_class = ListOrderSerializer
def get(self,request, format=None):
currency = request.GET.get('currency')
type = request.GET.get('type')
queryset = Order.objects.filter(currency=currency, type=type, status=0) # TODO status = 1 for orders that are Public
queryset = Order.objects.filter(currency=currency, type=type, status=int(Order.Status.PUB))
if len(queryset)== 0:
return Response({'not_found':'No orders found, be the first to make one'}, status=status.HTTP_404_NOT_FOUND)
queryset = queryset.order_by('created_at')
book_data = []
for order in queryset:
data = OrderSerializer(order).data
data = ListOrderSerializer(order).data
user = User.objects.filter(id=data['maker'])
if len(user) == 1:
data['maker_nick'] = user[0].username
# TODO avoid sending status and takers for book views
#data.pop('status','taker')
# Non participants should not see the status or who is the taker
for key in ('status','taker'):
del data[key]
book_data.append(data)
return Response(book_data, status=status.HTTP_200_OK)

View File

@ -0,0 +1,15 @@
#!/bin/bash
rm db.sqlite3
rm -R api/migrations
rm -R frontend/migrations
rm -R frontend/static/assets/avatars
python3 manage.py makemigrations
python3 manage.py makemigrations api
python3 manage.py migrate
python3 manage.py createsuperuser
python3 manage.py runserver

View File

@ -91,7 +91,7 @@ export default class MakerPage extends Component {
console.log(this.state)
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': csrftoken},
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')},
body: JSON.stringify({
type: this.state.type,
currency: this.state.currency,

View File

@ -2,6 +2,23 @@ import React, { Component } from "react";
import { Paper, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider} from "@material-ui/core"
import { Link } from 'react-router-dom'
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// pretty numbers
function pn(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@ -26,7 +43,7 @@ export default class OrderPage extends Component {
statusText: data.status_message,
type: data.type,
currency: data.currency,
currencyCode: (data.currency== 1 ) ? "USD": ((data.currency == 2 ) ? "EUR":"ETH"),
currencyCode: this.getCurrencyCode(data.currency),
amount: data.amount,
paymentMethod: data.payment_method,
isExplicit: data.is_explicit,
@ -41,10 +58,29 @@ export default class OrderPage extends Component {
});
}
// Gets currency code (3 letters) from numeric (e.g., 1 -> USD)
// Improve this function so currencies are read from json
getCurrencyCode(val){
return (val == 1 ) ? "USD": ((val == 2 ) ? "EUR":"ETH")
}
// Fix to use proper react props
handleClickBackButton=()=>{
window.history.back();
}
handleClickTakeOrderButton=()=>{
console.log(this.state)
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': csrftoken},
body: JSON.stringify({}),
};
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
.then((response) => response.json())
.then((data) => (console.log(data) & this.getOrderDetails(data.id)));
}
render (){
return (
<Grid container spacing={1}>
@ -53,7 +89,7 @@ export default class OrderPage extends Component {
BTC {this.state.type ? " Sell " : " Buy "} Order
</Typography>
<Paper elevation={12} style={{ padding: 8,}}>
<List component="nav" aria-label="mailbox folders">
<List dense="true">
<ListItem>
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Avatar
@ -89,6 +125,12 @@ export default class OrderPage extends Component {
{ this.state.takerNick!='None' ?
<><ListItem>
<ListItemText primary={this.state.takerNick} secondary="Order taker"/>
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Avatar
alt={this.state.makerNick}
src={window.location.origin +'/static/assets/avatars/' + this.state.takerNick + '.png'}
/>
</ListItemAvatar>
</ListItem>
<Divider /> </>: ""}
</>
@ -98,14 +140,16 @@ export default class OrderPage extends Component {
<ListItemText primary={'#'+this.orderId} secondary="Order ID"/>
</ListItem>
</List>
</Paper>
<Grid item xs={12} align="center">
{this.state.isParticipant ? "" : <Button variant='contained' color='primary' to='/home' component={Link}>Take Order</Button>}
{this.state.isParticipant ? "" : <Button variant='contained' color='primary' onClick={this.handleClickTakeOrderButton}>Take Order</Button>}
</Grid>
<Grid item xs={12} align="center">
<Button variant='contained' color='secondary' onClick={this.handleClickBackButton}>Back</Button>
</Grid>
</Paper>
</Grid>
</Grid>
);

View File

@ -74,7 +74,7 @@ export default class UserGenPage extends Component {
this.setState({
token: this.genBase62Token(32),
})
this.getGeneratedUser();
this.reload_for_csrf_to_work();
}
handleChangeToken=(e)=>{

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB