2022-01-03 12:11:33 +00:00
|
|
|
from rest_framework import serializers, status
|
2022-01-01 22:34:23 +00:00
|
|
|
from rest_framework.views import APIView
|
|
|
|
from rest_framework.response import Response
|
2022-01-03 01:31:28 +00:00
|
|
|
from django.contrib.auth import authenticate, login, logout
|
2022-01-02 12:59:48 +00:00
|
|
|
from django.contrib.auth.models import User
|
2022-01-04 13:47:37 +00:00
|
|
|
from django.conf.urls.static import static
|
2022-01-02 12:59:48 +00:00
|
|
|
|
2022-01-01 22:34:23 +00:00
|
|
|
from .serializers import OrderSerializer, MakeOrderSerializer
|
|
|
|
from .models import Order
|
2022-01-01 22:13:27 +00:00
|
|
|
|
2022-01-02 15:19:49 +00:00
|
|
|
from .nick_generator.nick_generator import NickGenerator
|
|
|
|
from robohash import Robohash
|
2022-01-02 17:04:51 +00:00
|
|
|
from scipy.stats import entropy
|
|
|
|
from math import log2
|
|
|
|
import numpy as np
|
|
|
|
import hashlib
|
|
|
|
from pathlib import Path
|
2022-01-03 09:06:51 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from django.utils import timezone
|
2022-01-02 15:19:49 +00:00
|
|
|
|
2022-01-04 10:21:45 +00:00
|
|
|
# .env
|
|
|
|
expiration_time = 8
|
|
|
|
|
2022-01-03 19:13:39 +00:00
|
|
|
avatar_path = Path('frontend/static/assets/avatars')
|
|
|
|
avatar_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
2022-01-01 22:13:27 +00:00
|
|
|
# Create your views here.
|
2022-01-01 22:34:23 +00:00
|
|
|
|
|
|
|
class MakeOrder(APIView):
|
|
|
|
serializer_class = MakeOrderSerializer
|
|
|
|
|
2022-01-01 23:06:47 +00:00
|
|
|
def post(self,request):
|
2022-01-01 22:34:23 +00:00
|
|
|
serializer = self.serializer_class(data=request.data)
|
2022-01-02 12:59:48 +00:00
|
|
|
|
2022-01-01 22:34:23 +00:00
|
|
|
if serializer.is_valid():
|
|
|
|
otype = serializer.data.get('type')
|
|
|
|
currency = serializer.data.get('currency')
|
|
|
|
amount = serializer.data.get('amount')
|
2022-01-02 09:40:19 +00:00
|
|
|
payment_method = serializer.data.get('payment_method')
|
2022-01-01 22:34:23 +00:00
|
|
|
premium = serializer.data.get('premium')
|
|
|
|
satoshis = serializer.data.get('satoshis')
|
2022-01-03 12:11:33 +00:00
|
|
|
is_explicit = serializer.data.get('is_explicit')
|
2022-01-03 01:31:28 +00:00
|
|
|
|
2022-01-01 22:34:23 +00:00
|
|
|
# query if the user is already a maker or taker, return error
|
2022-01-03 01:31:28 +00:00
|
|
|
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)
|
2022-01-01 22:34:23 +00:00
|
|
|
|
|
|
|
# Creates a new order in db
|
|
|
|
order = Order(
|
|
|
|
type=otype,
|
|
|
|
currency=currency,
|
|
|
|
amount=amount,
|
2022-01-02 09:40:19 +00:00
|
|
|
payment_method=payment_method,
|
2022-01-01 22:34:23 +00:00
|
|
|
premium=premium,
|
2022-01-03 01:31:28 +00:00
|
|
|
satoshis=satoshis,
|
2022-01-03 12:11:33 +00:00
|
|
|
is_explicit=is_explicit,
|
2022-01-04 10:21:45 +00:00
|
|
|
expires_at= timezone.now()+timedelta(hours=expiration_time),
|
2022-01-03 12:11:33 +00:00
|
|
|
maker=request.user)
|
2022-01-01 22:34:23 +00:00
|
|
|
order.save()
|
|
|
|
|
|
|
|
if not serializer.is_valid():
|
|
|
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
2022-01-02 12:59:48 +00:00
|
|
|
return Response(OrderSerializer(order).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
|
|
|
|
class OrderView(APIView):
|
|
|
|
serializer_class = OrderSerializer
|
|
|
|
lookup_url_kwarg = 'order_id'
|
|
|
|
|
|
|
|
def get(self, request, format=None):
|
|
|
|
order_id = request.GET.get(self.lookup_url_kwarg)
|
|
|
|
|
|
|
|
if order_id != None:
|
|
|
|
order = Order.objects.filter(id=order_id)
|
|
|
|
|
|
|
|
# check if exactly one order is found in the db
|
|
|
|
if len(order) == 1 :
|
|
|
|
order = order[0]
|
|
|
|
data = self.serializer_class(order).data
|
2022-01-03 01:31:28 +00:00
|
|
|
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)
|
2022-01-03 12:11:33 +00:00
|
|
|
|
|
|
|
#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.
|
2022-01-03 14:27:25 +00:00
|
|
|
|
2022-01-03 12:11:33 +00:00
|
|
|
data['maker_nick'] = str(order.maker)
|
|
|
|
data['taker_nick'] = str(order.taker)
|
2022-01-03 01:31:28 +00:00
|
|
|
|
|
|
|
if data['is_participant']:
|
|
|
|
return Response(data, status=status.HTTP_200_OK)
|
|
|
|
else:
|
|
|
|
# Non participants should not see the status or who is the taker
|
2022-01-03 12:11:33 +00:00
|
|
|
data.pop('status','status_message','taker','taker_nick')
|
2022-01-03 01:31:28 +00:00
|
|
|
return Response(data, status=status.HTTP_200_OK)
|
2022-01-02 12:59:48 +00:00
|
|
|
|
|
|
|
return Response({'Order Not Found':'Invalid Order Id'},status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
return Response({'Bad Request':'Order ID parameter not found in request'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
2022-01-02 15:19:49 +00:00
|
|
|
class UserGenerator(APIView):
|
|
|
|
lookup_url_kwarg = 'token'
|
|
|
|
NickGen = NickGenerator(
|
|
|
|
lang='English',
|
|
|
|
use_adv=False,
|
|
|
|
use_adj=True,
|
|
|
|
use_noun=True,
|
|
|
|
max_num=999)
|
|
|
|
|
2022-01-03 14:27:25 +00:00
|
|
|
def get(self,request, format=None):
|
2022-01-02 15:19:49 +00:00
|
|
|
'''
|
2022-01-02 17:04:51 +00:00
|
|
|
Get a new user derived from a high entropy token
|
2022-01-02 15:19:49 +00:00
|
|
|
|
|
|
|
- Request has a high-entropy token,
|
|
|
|
- Generates new nickname and avatar.
|
|
|
|
- Creates login credentials (new User object)
|
|
|
|
Response with Avatar and Nickname.
|
|
|
|
'''
|
2022-01-02 17:04:51 +00:00
|
|
|
token = request.GET.get(self.lookup_url_kwarg)
|
|
|
|
|
|
|
|
# Compute token entropy
|
|
|
|
value, counts = np.unique(list(token), return_counts=True)
|
|
|
|
shannon_entropy = entropy(counts, base=62)
|
|
|
|
bits_entropy = log2(len(value)**len(token))
|
|
|
|
|
|
|
|
# Start preparing payload
|
|
|
|
context = {'token_shannon_entropy': shannon_entropy, 'token_bits_entropy': bits_entropy}
|
|
|
|
|
|
|
|
# Deny user gen if entropy below 128 bits or 0.7 shannon heterogeneity
|
|
|
|
if bits_entropy < 128 or shannon_entropy < 0.7:
|
2022-01-02 18:27:40 +00:00
|
|
|
context['bad_request'] = 'The token does not have enough entropy'
|
2022-01-02 17:04:51 +00:00
|
|
|
return Response(context, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
# Hashes the token, only 1 iteration. Maybe more is better.
|
|
|
|
hash = hashlib.sha256(str.encode(token)).hexdigest()
|
|
|
|
|
|
|
|
# generate nickname
|
|
|
|
nickname = self.NickGen.short_from_SHA256(hash, max_length=18)[0]
|
|
|
|
context['nickname'] = nickname
|
|
|
|
|
|
|
|
# generate avatar
|
|
|
|
rh = Robohash(hash)
|
2022-01-04 13:47:37 +00:00
|
|
|
rh.assemble(roboset='set1', bgset='any')# for backgrounds ON
|
2022-01-03 22:52:46 +00:00
|
|
|
|
2022-01-04 13:47:37 +00:00
|
|
|
# Does not replace image if existing (avoid re-avatar in case of nick collusion)
|
2022-01-02 17:04:51 +00:00
|
|
|
|
2022-01-04 13:47:37 +00:00
|
|
|
image_path = avatar_path.joinpath(nickname+".png")
|
|
|
|
if not image_path.exists():
|
|
|
|
with open(image_path, "wb") as f:
|
|
|
|
rh.img.save(f, format="png")
|
|
|
|
|
|
|
|
# Create new credentials and logsin if nickname is new
|
2022-01-02 17:04:51 +00:00
|
|
|
if len(User.objects.filter(username=nickname)) == 0:
|
|
|
|
User.objects.create_user(username=nickname, password=token, is_staff=False)
|
2022-01-03 09:06:51 +00:00
|
|
|
user = authenticate(request, username=nickname, password=token)
|
2022-01-04 13:47:37 +00:00
|
|
|
user.profile.avatar = str(image_path)[9:] # removes frontend/ from url (ugly, to be fixed)
|
2022-01-03 01:31:28 +00:00
|
|
|
login(request, user)
|
2022-01-03 09:06:51 +00:00
|
|
|
return Response(context, status=status.HTTP_201_CREATED)
|
2022-01-02 17:04:51 +00:00
|
|
|
|
2022-01-03 09:06:51 +00:00
|
|
|
else:
|
|
|
|
user = authenticate(request, username=nickname, password=token)
|
|
|
|
if user is not None:
|
|
|
|
login(request, user)
|
|
|
|
# Sends the welcome back message, only if created +30 mins ago
|
2022-01-03 14:27:25 +00:00
|
|
|
if request.user.date_joined < (timezone.now()-timedelta(minutes=30)):
|
2022-01-03 09:06:51 +00:00
|
|
|
context['found'] = 'We found your Robosat. Welcome back!'
|
|
|
|
return Response(context, status=status.HTTP_202_ACCEPTED)
|
|
|
|
else:
|
2022-01-04 13:47:37 +00:00
|
|
|
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
|
2022-01-03 09:06:51 +00:00
|
|
|
context['found'] = 'Bad luck, this nickname is taken'
|
|
|
|
context['bad_request'] = 'Enter a different token'
|
|
|
|
return Response(context, status=status.HTTP_403_FORBIDDEN)
|
|
|
|
|
2022-01-03 01:31:28 +00:00
|
|
|
def delete(self,request):
|
|
|
|
user = User.objects.get(id = request.user.id)
|
|
|
|
|
2022-01-03 09:06:51 +00:00
|
|
|
# TO DO. Pressing give me another will delete the logged in user
|
|
|
|
# However it might be a long time recovered user
|
|
|
|
# Only delete if user live is < 5 minutes
|
|
|
|
|
2022-01-04 13:47:37 +00:00
|
|
|
# TODO check if user exists AND it is not a maker or taker!
|
2022-01-03 01:31:28 +00:00
|
|
|
if user is not None:
|
2022-01-03 19:13:39 +00:00
|
|
|
avatar_file = avatar_path.joinpath(str(request.user)+".png")
|
|
|
|
avatar_file.unlink() # Unsafe if avatar does not exist.
|
2022-01-03 01:31:28 +00:00
|
|
|
logout(request)
|
|
|
|
user.delete()
|
2022-01-03 19:13:39 +00:00
|
|
|
|
|
|
|
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_301_MOVED_PERMANENTLY)
|
2022-01-03 01:31:28 +00:00
|
|
|
|
|
|
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
2022-01-02 15:19:49 +00:00
|
|
|
|
2022-01-03 14:27:25 +00:00
|
|
|
class BookView(APIView):
|
|
|
|
serializer_class = OrderSerializer
|
|
|
|
|
|
|
|
def get(self,request, format=None):
|
2022-01-03 19:13:39 +00:00
|
|
|
currency = request.GET.get('currency')
|
2022-01-04 15:58:10 +00:00
|
|
|
type = request.GET.get('type')
|
2022-01-04 13:47:37 +00:00
|
|
|
queryset = Order.objects.filter(currency=currency, type=type, status=0) # TODO status = 1 for orders that are Public
|
2022-01-03 14:39:59 +00:00
|
|
|
if len(queryset)== 0:
|
2022-01-03 19:13:39 +00:00
|
|
|
return Response({'not_found':'No orders found, be the first to make one'}, status=status.HTTP_404_NOT_FOUND)
|
2022-01-03 14:39:59 +00:00
|
|
|
|
2022-01-04 15:58:10 +00:00
|
|
|
queryset = queryset.order_by('created_at')
|
2022-01-03 19:13:39 +00:00
|
|
|
book_data = []
|
|
|
|
for order in queryset:
|
2022-01-03 14:27:25 +00:00
|
|
|
data = OrderSerializer(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')
|
2022-01-03 19:13:39 +00:00
|
|
|
book_data.append(data)
|
2022-01-03 14:39:59 +00:00
|
|
|
|
|
|
|
return Response(book_data, status=status.HTTP_200_OK)
|
2022-01-03 14:27:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|