mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-05 22:10:10 +00:00
Merge branch '38-a-privacy-friendly-referral-program' into main
This commit is contained in:
commit
aa89fa603e
@ -89,6 +89,10 @@ FIAT_EXCHANGE_DURATION = 24
|
|||||||
PROPORTIONAL_ROUTING_FEE_LIMIT = 0.0002
|
PROPORTIONAL_ROUTING_FEE_LIMIT = 0.0002
|
||||||
# Base flat limit fee for routing in Sats (used only when proportional is lower than this)
|
# Base flat limit fee for routing in Sats (used only when proportional is lower than this)
|
||||||
MIN_FLAT_ROUTING_FEE_LIMIT = 10
|
MIN_FLAT_ROUTING_FEE_LIMIT = 10
|
||||||
|
MIN_FLAT_ROUTING_FEE_LIMIT_REWARD = 2
|
||||||
|
|
||||||
|
# Reward tip. Reward for every finished trade in the referral program (Satoshis)
|
||||||
|
REWARD_TIP = 100
|
||||||
|
|
||||||
# Username for HTLCs escrows
|
# Username for HTLCs escrows
|
||||||
ESCROW_USERNAME = 'admin'
|
ESCROW_USERNAME = 'admin'
|
||||||
|
@ -103,6 +103,7 @@ class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
|||||||
"avatar_tag",
|
"avatar_tag",
|
||||||
"id",
|
"id",
|
||||||
"user_link",
|
"user_link",
|
||||||
|
"is_referred",
|
||||||
"telegram_enabled",
|
"telegram_enabled",
|
||||||
"total_contracts",
|
"total_contracts",
|
||||||
"platform_rating",
|
"platform_rating",
|
||||||
|
@ -222,15 +222,15 @@ class LNNode:
|
|||||||
return payout
|
return payout
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pay_invoice(cls, invoice, num_satoshis):
|
def pay_invoice(cls, lnpayment):
|
||||||
"""Sends sats to buyer"""
|
"""Sends sats. Used for rewards payouts"""
|
||||||
|
|
||||||
fee_limit_sat = int(
|
fee_limit_sat = int(
|
||||||
max(
|
max(
|
||||||
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
|
lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
|
||||||
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
|
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
|
||||||
)) # 200 ppm or 10 sats
|
)) # 200 ppm or 10 sats
|
||||||
request = routerrpc.SendPaymentRequest(payment_request=invoice,
|
request = routerrpc.SendPaymentRequest(payment_request=lnpayment.invoice,
|
||||||
fee_limit_sat=fee_limit_sat,
|
fee_limit_sat=fee_limit_sat,
|
||||||
timeout_seconds=60)
|
timeout_seconds=60)
|
||||||
|
|
||||||
@ -238,15 +238,15 @@ class LNNode:
|
|||||||
metadata=[("macaroon",
|
metadata=[("macaroon",
|
||||||
MACAROON.hex())
|
MACAROON.hex())
|
||||||
]):
|
]):
|
||||||
print(response)
|
|
||||||
print(response.status)
|
|
||||||
|
|
||||||
# TODO ERROR HANDLING
|
|
||||||
if response.status == 0: # Status 0 'UNKNOWN'
|
if response.status == 0: # Status 0 'UNKNOWN'
|
||||||
|
# Not sure when this status happens
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if response.status == 1: # Status 1 'IN_FLIGHT'
|
if response.status == 1: # Status 1 'IN_FLIGHT'
|
||||||
return True, "In flight"
|
return True, "In flight"
|
||||||
if response.status == 3: # 4 'FAILED' ??
|
|
||||||
|
if response.status == 3: # Status 3 'FAILED'
|
||||||
"""0 Payment isn't failed (yet).
|
"""0 Payment isn't failed (yet).
|
||||||
1 There are more routes to try, but the payment timeout was exceeded.
|
1 There are more routes to try, but the payment timeout was exceeded.
|
||||||
2 All possible routes were tried and failed permanently. Or were no routes to the destination at all.
|
2 All possible routes were tried and failed permanently. Or were no routes to the destination at all.
|
||||||
@ -256,12 +256,10 @@ class LNNode:
|
|||||||
"""
|
"""
|
||||||
context = cls.payment_failure_context[response.failure_reason]
|
context = cls.payment_failure_context[response.failure_reason]
|
||||||
return False, context
|
return False, context
|
||||||
|
|
||||||
if response.status == 2: # STATUS 'SUCCEEDED'
|
if response.status == 2: # STATUS 'SUCCEEDED'
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
# How to catch the errors like:"grpc_message":"invoice is already paid","grpc_status":6}
|
|
||||||
# These are not in the response only printed to commandline
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -384,8 +384,10 @@ class Logics:
|
|||||||
|
|
||||||
fee_sats = order.last_satoshis * fee_fraction
|
fee_sats = order.last_satoshis * fee_fraction
|
||||||
|
|
||||||
|
reward_tip = int(config('REWARD_TIP')) if user.profile.is_referred else 0
|
||||||
|
|
||||||
if cls.is_buyer(order, user):
|
if cls.is_buyer(order, user):
|
||||||
invoice_amount = round(order.last_satoshis - fee_sats) # Trading fee to buyer is charged here.
|
invoice_amount = round(order.last_satoshis - fee_sats - reward_tip) # Trading fee to buyer is charged here.
|
||||||
|
|
||||||
return True, {"invoice_amount": invoice_amount}
|
return True, {"invoice_amount": invoice_amount}
|
||||||
|
|
||||||
@ -399,10 +401,12 @@ class Logics:
|
|||||||
elif user == order.taker:
|
elif user == order.taker:
|
||||||
fee_fraction = FEE * (1 - MAKER_FEE_SPLIT)
|
fee_fraction = FEE * (1 - MAKER_FEE_SPLIT)
|
||||||
|
|
||||||
fee_sats = order.last_satoshis * fee_fraction
|
fee_sats = order.last_satoshis * fee_fraction
|
||||||
|
|
||||||
|
reward_tip = int(config('REWARD_TIP')) if user.profile.is_referred else 0
|
||||||
|
|
||||||
if cls.is_seller(order, user):
|
if cls.is_seller(order, user):
|
||||||
escrow_amount = round(order.last_satoshis + fee_sats) # Trading fee to seller is charged here.
|
escrow_amount = round(order.last_satoshis + fee_sats + reward_tip) # Trading fee to seller is charged here.
|
||||||
|
|
||||||
return True, {"escrow_amount": escrow_amount}
|
return True, {"escrow_amount": escrow_amount}
|
||||||
|
|
||||||
@ -1029,12 +1033,19 @@ class Logics:
|
|||||||
cls.return_bond(order.taker_bond)
|
cls.return_bond(order.taker_bond)
|
||||||
cls.return_bond(order.maker_bond)
|
cls.return_bond(order.maker_bond)
|
||||||
##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
|
##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
|
||||||
##### Backgroun process "follow_invoices" will try to pay this invoice until success
|
##### Background process "follow_invoices" will try to pay this invoice until success
|
||||||
order.status = Order.Status.PAY
|
order.status = Order.Status.PAY
|
||||||
order.payout.status = LNPayment.Status.FLIGHT
|
order.payout.status = LNPayment.Status.FLIGHT
|
||||||
order.payout.save()
|
order.payout.save()
|
||||||
order.save()
|
order.save()
|
||||||
send_message.delay(order.id,'trade_successful')
|
send_message.delay(order.id,'trade_successful')
|
||||||
|
|
||||||
|
# Add referral rewards (safe)
|
||||||
|
try:
|
||||||
|
Logics.add_rewards(order)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -1082,3 +1093,57 @@ class Logics:
|
|||||||
user.profile.save()
|
user.profile.save()
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_rewards(cls, order):
|
||||||
|
'''
|
||||||
|
This function is called when a trade is finished.
|
||||||
|
If participants of the order were referred, the reward is given to the referees.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if order.maker.profile.is_referred:
|
||||||
|
profile = order.maker.profile.referred_by
|
||||||
|
profile.pending_rewards += int(config('REWARD_TIP'))
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
if order.taker.profile.is_referred:
|
||||||
|
profile = order.taker.profile.referred_by
|
||||||
|
profile.pending_rewards += int(config('REWARD_TIP'))
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def withdraw_rewards(cls, user, invoice):
|
||||||
|
|
||||||
|
# only a user with positive withdraw balance can use this
|
||||||
|
|
||||||
|
if user.profile.earned_rewards < 1:
|
||||||
|
return False, {"bad_invoice": "You have not earned rewards"}
|
||||||
|
|
||||||
|
num_satoshis = user.profile.earned_rewards
|
||||||
|
reward_payout = LNNode.validate_ln_invoice(invoice, num_satoshis)
|
||||||
|
|
||||||
|
if not reward_payout["valid"]:
|
||||||
|
return False, reward_payout["context"]
|
||||||
|
|
||||||
|
lnpayment = LNPayment.objects.create(
|
||||||
|
concept= LNPayment.Concepts.WITHREWA,
|
||||||
|
type= LNPayment.Types.NORM,
|
||||||
|
sender= User.objects.get(username=ESCROW_USERNAME),
|
||||||
|
status= LNPayment.Status.VALIDI,
|
||||||
|
receiver=user,
|
||||||
|
invoice= invoice,
|
||||||
|
num_satoshis= num_satoshis,
|
||||||
|
description= reward_payout["description"],
|
||||||
|
payment_hash= reward_payout["payment_hash"],
|
||||||
|
created_at= reward_payout["created_at"],
|
||||||
|
expires_at= reward_payout["expires_at"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if LNNode.pay_invoice(lnpayment):
|
||||||
|
user.profile.earned_rewards = 0
|
||||||
|
user.profile.claimed_rewards += num_satoshis
|
||||||
|
user.profile.save()
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ class LNPayment(models.Model):
|
|||||||
TAKEBOND = 1, "Taker bond"
|
TAKEBOND = 1, "Taker bond"
|
||||||
TRESCROW = 2, "Trade escrow"
|
TRESCROW = 2, "Trade escrow"
|
||||||
PAYBUYER = 3, "Payment to buyer"
|
PAYBUYER = 3, "Payment to buyer"
|
||||||
|
WITHREWA = 4, "Withdraw rewards"
|
||||||
|
|
||||||
class Status(models.IntegerChoices):
|
class Status(models.IntegerChoices):
|
||||||
INVGEN = 0, "Generated"
|
INVGEN = 0, "Generated"
|
||||||
@ -405,6 +406,32 @@ class Profile(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
null=False
|
null=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Referral program
|
||||||
|
is_referred = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
referred_by = models.ForeignKey(
|
||||||
|
'self',
|
||||||
|
related_name="referee",
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
referral_code = models.CharField(
|
||||||
|
max_length=15,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
# Recent rewards from referred trades that will be "earned" at a later point to difficult spionage.
|
||||||
|
pending_rewards = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
# Claimable rewards
|
||||||
|
earned_rewards = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
# Total claimed rewards
|
||||||
|
claimed_rewards = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
|
||||||
# Disputes
|
# Disputes
|
||||||
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||||
lost_disputes = models.PositiveIntegerField(null=False, default=0)
|
lost_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||||
|
@ -66,3 +66,9 @@ class UpdateOrderSerializer(serializers.Serializer):
|
|||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ClaimRewardSerializer(serializers.Serializer):
|
||||||
|
invoice = serializers.CharField(max_length=2000,
|
||||||
|
allow_null=True,
|
||||||
|
allow_blank=True,
|
||||||
|
default=None)
|
27
api/tasks.py
27
api/tasks.py
@ -17,9 +17,11 @@ def users_cleansing():
|
|||||||
queryset = User.objects.filter(~Q(last_login__range=active_time_range))
|
queryset = User.objects.filter(~Q(last_login__range=active_time_range))
|
||||||
queryset = queryset.filter(is_staff=False) # Do not delete staff users
|
queryset = queryset.filter(is_staff=False) # Do not delete staff users
|
||||||
|
|
||||||
# And do not have an active trade or any past contract.
|
# And do not have an active trade, any past contract or any reward.
|
||||||
deleted_users = []
|
deleted_users = []
|
||||||
for user in queryset:
|
for user in queryset:
|
||||||
|
if user.profile.pending_rewards > 0 or user.profile.earned_rewards > 0 or user.profile.claimed_rewards > 0:
|
||||||
|
continue
|
||||||
if not user.profile.total_contracts == 0:
|
if not user.profile.total_contracts == 0:
|
||||||
continue
|
continue
|
||||||
valid, _, _ = Logics.validate_already_maker_or_taker(user)
|
valid, _, _ = Logics.validate_already_maker_or_taker(user)
|
||||||
@ -33,6 +35,28 @@ def users_cleansing():
|
|||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@shared_task(name="give_rewards")
|
||||||
|
def users_cleansing():
|
||||||
|
"""
|
||||||
|
Referral rewards go from pending to earned.
|
||||||
|
Happens asynchronously so the referral program cannot be easily used to spy.
|
||||||
|
"""
|
||||||
|
from api.models import Profile
|
||||||
|
|
||||||
|
# Users who's last login has not been in the last 6 hours
|
||||||
|
queryset = Profile.objects.filter(pending_rewards__gt=0)
|
||||||
|
|
||||||
|
# And do not have an active trade, any past contract or any reward.
|
||||||
|
results = {}
|
||||||
|
for profile in queryset:
|
||||||
|
given_reward = profile.pending_rewards
|
||||||
|
profile.earned_rewards += given_reward
|
||||||
|
profile.pending_rewards = 0
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
results[profile.user.username] = {'given_reward':given_reward,'earned_rewards':profile.earned_rewards}
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
@shared_task(name="follow_send_payment")
|
@shared_task(name="follow_send_payment")
|
||||||
def follow_send_payment(lnpayment):
|
def follow_send_payment(lnpayment):
|
||||||
@ -45,6 +69,7 @@ def follow_send_payment(lnpayment):
|
|||||||
|
|
||||||
from api.lightning.node import LNNode, MACAROON
|
from api.lightning.node import LNNode, MACAROON
|
||||||
from api.models import LNPayment, Order
|
from api.models import LNPayment, Order
|
||||||
|
from api.logics import Logics
|
||||||
|
|
||||||
fee_limit_sat = int(
|
fee_limit_sat = int(
|
||||||
max(
|
max(
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import MakerView, OrderView, UserView, BookView, InfoView
|
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("make/", MakerView.as_view()),
|
path("make/", MakerView.as_view()),
|
||||||
path(
|
path("order/",OrderView.as_view({
|
||||||
"order/",
|
|
||||||
OrderView.as_view({
|
|
||||||
"get": "get",
|
"get": "get",
|
||||||
"post": "take_update_confirm_dispute_cancel"
|
"post": "take_update_confirm_dispute_cancel"
|
||||||
}),
|
}),
|
||||||
@ -14,4 +12,5 @@ urlpatterns = [
|
|||||||
path("book/", BookView.as_view()),
|
path("book/", BookView.as_view()),
|
||||||
# path('robot/') # Profile Info
|
# path('robot/') # Profile Info
|
||||||
path("info/", InfoView.as_view()),
|
path("info/", InfoView.as_view()),
|
||||||
|
path("reward/", RewardView.as_view()),
|
||||||
]
|
]
|
||||||
|
67
api/views.py
67
api/views.py
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from re import T
|
from re import T
|
||||||
from django.db.models import query
|
from django.db.models import Sum
|
||||||
from rest_framework import status, viewsets
|
from rest_framework import status, viewsets
|
||||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@ -9,10 +9,11 @@ from rest_framework.response import Response
|
|||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer
|
||||||
from api.models import LNPayment, MarketTick, Order, Currency
|
from api.models import LNPayment, MarketTick, Order, Currency, Profile
|
||||||
from api.logics import Logics
|
from api.logics import Logics
|
||||||
from api.messages import Telegram
|
from api.messages import Telegram
|
||||||
|
from secrets import token_urlsafe
|
||||||
from api.utils import get_lnd_version, get_commit_robosats, compute_premium_percentile
|
from api.utils import get_lnd_version, get_commit_robosats, compute_premium_percentile
|
||||||
|
|
||||||
from .nick_generator.nick_generator import NickGenerator
|
from .nick_generator.nick_generator import NickGenerator
|
||||||
@ -445,7 +446,6 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class UserView(APIView):
|
class UserView(APIView):
|
||||||
lookup_url_kwarg = "token"
|
|
||||||
NickGen = NickGenerator(lang="English",
|
NickGen = NickGenerator(lang="English",
|
||||||
use_adv=False,
|
use_adv=False,
|
||||||
use_adj=True,
|
use_adj=True,
|
||||||
@ -475,12 +475,8 @@ class UserView(APIView):
|
|||||||
"bad_request"] = f"You are already logged in as {request.user} and have an active order"
|
"bad_request"] = f"You are already logged in as {request.user} and have an active order"
|
||||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Does not allow this 'mistake' if the last login was sometime ago (5 minutes)
|
token = request.GET.get("token")
|
||||||
# if request.user.last_login < timezone.now() - timedelta(minutes=5):
|
ref_code = request.GET.get("ref_code")
|
||||||
# context['bad_request'] = f'You are already logged in as {request.user}'
|
|
||||||
# return Response(context, status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
token = request.GET.get(self.lookup_url_kwarg)
|
|
||||||
|
|
||||||
# Compute token entropy
|
# Compute token entropy
|
||||||
value, counts = np.unique(list(token), return_counts=True)
|
value, counts = np.unique(list(token), return_counts=True)
|
||||||
@ -514,14 +510,27 @@ class UserView(APIView):
|
|||||||
with open(image_path, "wb") as f:
|
with open(image_path, "wb") as f:
|
||||||
rh.img.save(f, format="png")
|
rh.img.save(f, format="png")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create new credentials and login if nickname is new
|
# Create new credentials and login if nickname is new
|
||||||
if len(User.objects.filter(username=nickname)) == 0:
|
if len(User.objects.filter(username=nickname)) == 0:
|
||||||
User.objects.create_user(username=nickname,
|
User.objects.create_user(username=nickname,
|
||||||
password=token,
|
password=token,
|
||||||
is_staff=False)
|
is_staff=False)
|
||||||
user = authenticate(request, username=nickname, password=token)
|
user = authenticate(request, username=nickname, password=token)
|
||||||
user.profile.avatar = "static/assets/avatars/" + nickname + ".png"
|
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
|
||||||
|
context['referral_code'] = token_urlsafe(8)
|
||||||
|
user.profile.referral_code = context['referral_code']
|
||||||
|
user.profile.avatar = "static/assets/avatars/" + nickname + ".png"
|
||||||
|
|
||||||
|
# If the ref_code was created by another robot, this robot was referred.
|
||||||
|
queryset = Profile.objects.filter(referral_code=ref_code)
|
||||||
|
if len(queryset) == 1:
|
||||||
|
user.profile.is_referred = True
|
||||||
|
user.profile.referred_by = queryset[0]
|
||||||
|
|
||||||
|
user.profile.save()
|
||||||
return Response(context, status=status.HTTP_201_CREATED)
|
return Response(context, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -635,6 +644,8 @@ class InfoView(ListAPIView):
|
|||||||
context["num_public_sell_orders"] = len(
|
context["num_public_sell_orders"] = len(
|
||||||
Order.objects.filter(type=Order.Types.SELL,
|
Order.objects.filter(type=Order.Types.SELL,
|
||||||
status=Order.Status.PUB))
|
status=Order.Status.PUB))
|
||||||
|
context["book_liquidity"] = Order.objects.filter(status=Order.Status.PUB).aggregate(Sum('last_satoshis'))['last_satoshis__sum']
|
||||||
|
context["book_liquidity"] = 0 if context["book_liquidity"] == None else context["book_liquidity"]
|
||||||
|
|
||||||
# Number of active users (logged in in last 30 minutes)
|
# Number of active users (logged in in last 30 minutes)
|
||||||
today = datetime.today()
|
today = datetime.today()
|
||||||
@ -679,11 +690,45 @@ class InfoView(ListAPIView):
|
|||||||
context["maker_fee"] = float(config("FEE"))*float(config("MAKER_FEE_SPLIT"))
|
context["maker_fee"] = float(config("FEE"))*float(config("MAKER_FEE_SPLIT"))
|
||||||
context["taker_fee"] = float(config("FEE"))*(1 - float(config("MAKER_FEE_SPLIT")))
|
context["taker_fee"] = float(config("FEE"))*(1 - float(config("MAKER_FEE_SPLIT")))
|
||||||
context["bond_size"] = float(config("BOND_SIZE"))
|
context["bond_size"] = float(config("BOND_SIZE"))
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
context["nickname"] = request.user.username
|
context["nickname"] = request.user.username
|
||||||
|
context["referral_link"] = str(config('HOST_NAME'))+'/ref/'+str(request.user.profile.referral_code)
|
||||||
|
context["earned_rewards"] = request.user.profile.earned_rewards
|
||||||
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
|
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
|
||||||
request.user)
|
request.user)
|
||||||
if not has_no_active_order:
|
if not has_no_active_order:
|
||||||
context["active_order_id"] = order.id
|
context["active_order_id"] = order.id
|
||||||
|
|
||||||
return Response(context, status.HTTP_200_OK)
|
return Response(context, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class RewardView(CreateAPIView):
|
||||||
|
serializer_class = ClaimRewardSerializer
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"bad_request":
|
||||||
|
"Woops! It seems you do not have a robot avatar"
|
||||||
|
},
|
||||||
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
invoice = serializer.data.get("invoice")
|
||||||
|
|
||||||
|
valid, context = Logics.withdraw_rewards(request.user, invoice)
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
context['successful_withdrawal'] = False
|
||||||
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Response({"successful_withdrawal": True}, status.HTTP_200_OK)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import {Badge, Tooltip, TextField, ListItemAvatar, Avatar,Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material";
|
import {Chip, CircularProgress, Badge, Tooltip, TextField, ListItemAvatar, Button, Avatar,Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material";
|
||||||
import MediaQuery from 'react-responsive'
|
import MediaQuery from 'react-responsive'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
@ -21,14 +21,37 @@ import PasswordIcon from '@mui/icons-material/Password';
|
|||||||
import ContentCopy from "@mui/icons-material/ContentCopy";
|
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import WebIcon from '@mui/icons-material/Web';
|
import WebIcon from '@mui/icons-material/Web';
|
||||||
|
import BookIcon from '@mui/icons-material/Book';
|
||||||
|
import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt';
|
||||||
|
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
|
||||||
|
|
||||||
// pretty numbers
|
// pretty numbers
|
||||||
function pn(x) {
|
function pn(x) {
|
||||||
var parts = x.toString().split(".");
|
if(x == null){
|
||||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
return 'null'
|
||||||
return parts.join(".");
|
}else{
|
||||||
|
var parts = x.toString().split(".");
|
||||||
|
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
return parts.join(".");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export default class BottomBar extends Component {
|
export default class BottomBar extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -36,8 +59,10 @@ export default class BottomBar extends Component {
|
|||||||
openStatsForNerds: false,
|
openStatsForNerds: false,
|
||||||
openCommuniy: false,
|
openCommuniy: false,
|
||||||
openExchangeSummary:false,
|
openExchangeSummary:false,
|
||||||
|
openClaimRewards: false,
|
||||||
num_public_buy_orders: 0,
|
num_public_buy_orders: 0,
|
||||||
num_public_sell_orders: 0,
|
num_public_sell_orders: 0,
|
||||||
|
book_liquidity: 0,
|
||||||
active_robots_today: 0,
|
active_robots_today: 0,
|
||||||
maker_fee: 0,
|
maker_fee: 0,
|
||||||
taker_fee: 0,
|
taker_fee: 0,
|
||||||
@ -49,6 +74,12 @@ export default class BottomBar extends Component {
|
|||||||
profileShown: false,
|
profileShown: false,
|
||||||
alternative_site: 'robosats...',
|
alternative_site: 'robosats...',
|
||||||
node_id: '00000000',
|
node_id: '00000000',
|
||||||
|
referral_link: 'No referral link',
|
||||||
|
earned_rewards: 0,
|
||||||
|
rewardInvoice: null,
|
||||||
|
badInvoice: false,
|
||||||
|
showRewardsSpinner: false,
|
||||||
|
withdrawn: false,
|
||||||
};
|
};
|
||||||
this.getInfo();
|
this.getInfo();
|
||||||
}
|
}
|
||||||
@ -215,6 +246,30 @@ export default class BottomBar extends Component {
|
|||||||
this.setState({openProfile: false});
|
this.setState({openProfile: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleSubmitInvoiceClicked=()=>{
|
||||||
|
this.setState({
|
||||||
|
badInvoice:false,
|
||||||
|
showRewardsSpinner: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'invoice': this.state.rewardInvoice,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
fetch('/api/reward/', requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => console.log(data) & this.setState({
|
||||||
|
badInvoice:data.bad_invoice,
|
||||||
|
openClaimRewards: data.successful_withdrawal ? false : true,
|
||||||
|
earned_rewards: data.successful_withdrawal ? 0 : this.state.earned_rewards,
|
||||||
|
withdrawn: data.successful_withdrawal ? true : false,
|
||||||
|
showRewardsSpinner: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
dialogProfile =() =>{
|
dialogProfile =() =>{
|
||||||
return(
|
return(
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -241,6 +296,7 @@ export default class BottomBar extends Component {
|
|||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Divider/>
|
<Divider/>
|
||||||
{this.state.active_order_id ?
|
{this.state.active_order_id ?
|
||||||
// TODO Link to router and do this.props.history.push
|
// TODO Link to router and do this.props.history.push
|
||||||
@ -258,6 +314,7 @@ export default class BottomBar extends Component {
|
|||||||
<ListItemText primary="No active orders" secondary="Your current order"/>
|
<ListItemText primary="No active orders" secondary="Your current order"/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<PasswordIcon/>
|
<PasswordIcon/>
|
||||||
@ -266,21 +323,97 @@ export default class BottomBar extends Component {
|
|||||||
{this.props.token ?
|
{this.props.token ?
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
disabled
|
||||||
label='Store safely'
|
label='Store Safely'
|
||||||
value={this.props.token }
|
value={this.props.token }
|
||||||
variant='filled'
|
variant='filled'
|
||||||
size='small'
|
size='small'
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment:
|
endAdornment:
|
||||||
<IconButton onClick= {()=>navigator.clipboard.writeText(this.props.token)}>
|
<Tooltip disableHoverListener enterTouchDelay="0" title="Copied!">
|
||||||
<ContentCopy />
|
<IconButton onClick= {()=>navigator.clipboard.writeText(this.props.token)}>
|
||||||
</IconButton>,
|
<ContentCopy />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
'Cannot remember'}
|
'Cannot remember'}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider><Chip label='Earn Sats'/></Divider>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PersonAddAltIcon/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Share to earn 100 Sats per trade">
|
||||||
|
<TextField
|
||||||
|
label='Your Referral Link'
|
||||||
|
value={this.state.referral_link}
|
||||||
|
// variant='filled'
|
||||||
|
size='small'
|
||||||
|
InputProps={{
|
||||||
|
endAdornment:
|
||||||
|
<Tooltip disableHoverListener enterTouchDelay="0" title="Copied!">
|
||||||
|
<IconButton onClick= {()=>navigator.clipboard.writeText(this.state.referral_link)}>
|
||||||
|
<ContentCopy />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<EmojiEventsIcon/>
|
||||||
|
</ListItemIcon>
|
||||||
|
{!this.state.openClaimRewards ?
|
||||||
|
<ListItemText secondary="Your earned rewards">
|
||||||
|
<Grid container xs={12}>
|
||||||
|
<Grid item xs={9}>
|
||||||
|
<Typography>{this.state.earned_rewards+" Sats"}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<Button disabled={this.state.earned_rewards==0? true : false} onClick={() => this.setState({openClaimRewards:true})} variant="contained" size="small">Claim</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItemText>
|
||||||
|
:
|
||||||
|
<form style={{maxWidth: 270}}>
|
||||||
|
<Grid containter alignItems="stretch" style={{ display: "flex"}} align="center">
|
||||||
|
<Grid item alignItems="stretch" style={{ display: "flex" }} align="center">
|
||||||
|
<TextField
|
||||||
|
error={this.state.badInvoice}
|
||||||
|
helperText={this.state.badInvoice ? this.state.badInvoice : "" }
|
||||||
|
label={"Invoice for " + this.state.earned_rewards + " Sats"}
|
||||||
|
//variant="standard"
|
||||||
|
size="small"
|
||||||
|
value={this.state.rewardInvoice}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ rewardInvoice: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item alignItems="stretch" style={{ display: "flex" }}>
|
||||||
|
<Button sx={{maxHeight:38}} onClick={this.handleSubmitInvoiceClicked} variant="contained" color="primary" size="small" > Submit </Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{this.state.showRewardsSpinner?
|
||||||
|
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||||
|
<CircularProgress/>
|
||||||
|
</div>
|
||||||
|
:""}
|
||||||
|
|
||||||
|
{this.state.withdrawn?
|
||||||
|
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||||
|
<Typography color="primary" variant="body2"><b>There it goes, thank you!🥇</b></Typography>
|
||||||
|
</div>
|
||||||
|
:""}
|
||||||
|
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -465,6 +598,18 @@ bottomBarDesktop =()=>{
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
|
<ListItem >
|
||||||
|
<ListItemIcon size="small">
|
||||||
|
<BookIcon/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primaryTypographyProps={{fontSize: '14px'}}
|
||||||
|
secondaryTypographyProps={{fontSize: '12px'}}
|
||||||
|
primary={pn(this.state.book_liquidity)+" Sats"}
|
||||||
|
secondary="Book liquidity" />
|
||||||
|
</ListItem>
|
||||||
|
<Divider/>
|
||||||
|
|
||||||
<ListItem >
|
<ListItem >
|
||||||
<ListItemIcon size="small">
|
<ListItemIcon size="small">
|
||||||
<SmartToyIcon/>
|
<SmartToyIcon/>
|
||||||
|
@ -30,7 +30,8 @@ export default class HomePage extends Component {
|
|||||||
<Router >
|
<Router >
|
||||||
<div className='appCenter'>
|
<div className='appCenter'>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path='/' render={(props) => <UserGenPage {...this.state} setAppState={this.setAppState}/>}/>
|
<Route exact path='/' render={(props) => <UserGenPage {...props} {...this.state} setAppState={this.setAppState}/>}/>
|
||||||
|
<Route path='/ref/:refCode' render={(props) => <UserGenPage {...props} {...this.state} setAppState={this.setAppState}/>}/>
|
||||||
<Route path='/make' component={MakerPage}/>
|
<Route path='/make' component={MakerPage}/>
|
||||||
<Route path='/book' component={BookPage}/>
|
<Route path='/book' component={BookPage}/>
|
||||||
<Route path="/order/:orderId" component={OrderPage}/>
|
<Route path="/order/:orderId" component={OrderPage}/>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup, dividerClasses} from "@mui/material"
|
import { Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material"
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import getFlags from './getFlags'
|
import getFlags from './getFlags'
|
||||||
|
|
||||||
|
@ -815,17 +815,17 @@ handleRatingRobosatsChange=(e)=>{
|
|||||||
🎉Trade finished!🥳
|
🎉Trade finished!🥳
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
{/* <Grid item xs={12} align="center">
|
||||||
<Typography component="body2" variant="body2" align="center">
|
<Typography component="body2" variant="body2" align="center">
|
||||||
What do you think of ⚡<b>{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}</b>⚡?
|
What do you think of ⚡<b>{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}</b>⚡?
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Rating name="size-large" defaultValue={0} size="large" onChange={this.handleRatingUserChange} />
|
<Rating name="size-large" defaultValue={0} size="large" onChange={this.handleRatingUserChange} />
|
||||||
</Grid>
|
</Grid> */}
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Typography component="body2" variant="body2" align="center">
|
<Typography component="body2" variant="body2" align="center">
|
||||||
What do you think of 🤖<b>RoboSats</b>🤖?
|
What do you think of 🤖<b>RoboSats</b>⚡?
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
@ -834,7 +834,7 @@ handleRatingRobosatsChange=(e)=>{
|
|||||||
{this.state.rating_platform==5 ?
|
{this.state.rating_platform==5 ?
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Typography component="body2" variant="body2" align="center">
|
<Typography component="body2" variant="body2" align="center">
|
||||||
<p>Thank you! RoboSats loves you too ❤️</p>
|
<p><b>Thank you! RoboSats loves you too ❤️</b></p>
|
||||||
<p>RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!</p>
|
<p>RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -842,8 +842,9 @@ handleRatingRobosatsChange=(e)=>{
|
|||||||
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
|
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Typography component="body2" variant="body2" align="center">
|
<Typography component="body2" variant="body2" align="center">
|
||||||
Thank you for using Robosats! Let us know what you did not like and how the platform could improve
|
<p><b>Thank you for using Robosats!</b></p>
|
||||||
(<a href="https://t.me/robosats">Telegram</a> / <a href="https://github.com/Reckless-Satoshi/robosats/issues">Github</a>)
|
<p>Let us know how the platform could improve
|
||||||
|
(<a href="https://t.me/robosats">Telegram</a> / <a href="https://github.com/Reckless-Satoshi/robosats/issues">Github</a>)</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
: null}
|
: null}
|
||||||
@ -902,7 +903,9 @@ handleRatingRobosatsChange=(e)=>{
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Typography component="body2" variant="body2" align="center">
|
<Typography component="body2" variant="body2" align="center">
|
||||||
Your invoice has expires or more than 3 payments attempts have been made.
|
Your invoice has expired or more than 3 payment attempts have been made.
|
||||||
|
Muun is not recommended, <a href="https://github.com/Reckless-Satoshi/robosats/issues/44">check the list of
|
||||||
|
compatible wallets</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
|
@ -32,7 +32,7 @@ export default class UserGenPage extends Component {
|
|||||||
tokenHasChanged: false,
|
tokenHasChanged: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
//this.props.setAppState({avatarLoaded: false, nickname: null, token: null});
|
this.refCode = this.props.match.params.refCode;
|
||||||
|
|
||||||
// Checks in parent HomePage if there is already a nick and token
|
// Checks in parent HomePage if there is already a nick and token
|
||||||
// Displays the existing one
|
// Displays the existing one
|
||||||
@ -40,7 +40,7 @@ export default class UserGenPage extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
nickname: this.props.nickname,
|
nickname: this.props.nickname,
|
||||||
token: this.props.token? this.props.token : null,
|
token: this.props.token? this.props.token : null,
|
||||||
avatar_url: 'static/assets/avatars/' + this.props.nickname + '.png',
|
avatar_url: '/static/assets/avatars/' + this.props.nickname + '.png',
|
||||||
loadingRobot: false
|
loadingRobot: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,13 +65,13 @@ export default class UserGenPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getGeneratedUser=(token)=>{
|
getGeneratedUser=(token)=>{
|
||||||
fetch('/api/user' + '?token=' + token)
|
fetch('/api/user' + '?token=' + token + '&ref_code=' + this.refCode)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
nickname: data.nickname,
|
nickname: data.nickname,
|
||||||
bit_entropy: data.token_bits_entropy,
|
bit_entropy: data.token_bits_entropy,
|
||||||
avatar_url: 'static/assets/avatars/' + data.nickname + '.png',
|
avatar_url: '/static/assets/avatars/' + data.nickname + '.png',
|
||||||
shannon_entropy: data.token_shannon_entropy,
|
shannon_entropy: data.token_shannon_entropy,
|
||||||
bad_request: data.bad_request,
|
bad_request: data.bad_request,
|
||||||
found: data.found,
|
found: data.found,
|
||||||
|
File diff suppressed because one or more lines are too long
@ -758,6 +758,10 @@
|
|||||||
!*** ./node_modules/@mui/icons-material/ContentCopy.js ***!
|
!*** ./node_modules/@mui/icons-material/ContentCopy.js ***!
|
||||||
\*********************************************************/
|
\*********************************************************/
|
||||||
|
|
||||||
|
/*!*********************************************************!*\
|
||||||
|
!*** ./node_modules/@mui/icons-material/EmojiEvents.js ***!
|
||||||
|
\*********************************************************/
|
||||||
|
|
||||||
/*!*********************************************************!*\
|
/*!*********************************************************!*\
|
||||||
!*** ./node_modules/@mui/icons-material/PriceChange.js ***!
|
!*** ./node_modules/@mui/icons-material/PriceChange.js ***!
|
||||||
\*********************************************************/
|
\*********************************************************/
|
||||||
@ -838,6 +842,10 @@
|
|||||||
!*** ./node_modules/@mui/base/utils/appendOwnerState.js ***!
|
!*** ./node_modules/@mui/base/utils/appendOwnerState.js ***!
|
||||||
\**********************************************************/
|
\**********************************************************/
|
||||||
|
|
||||||
|
/*!**********************************************************!*\
|
||||||
|
!*** ./node_modules/@mui/icons-material/PersonAddAlt.js ***!
|
||||||
|
\**********************************************************/
|
||||||
|
|
||||||
/*!**********************************************************!*\
|
/*!**********************************************************!*\
|
||||||
!*** ./node_modules/@mui/material/Alert/alertClasses.js ***!
|
!*** ./node_modules/@mui/material/Alert/alertClasses.js ***!
|
||||||
\**********************************************************/
|
\**********************************************************/
|
||||||
|
@ -2,11 +2,9 @@ from django.urls import path
|
|||||||
from .views import index
|
from .views import index
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index),
|
|
||||||
path("info/", index),
|
|
||||||
path("login/", index),
|
|
||||||
path("make/", index),
|
path("make/", index),
|
||||||
path("book/", index),
|
path("book/", index),
|
||||||
path("order/<int:orderId>", index),
|
path("order/<int:orderId>", index),
|
||||||
path("wait/", index),
|
path("", index),
|
||||||
|
path("ref/<refCode>", index),
|
||||||
]
|
]
|
||||||
|
@ -31,9 +31,13 @@ app.conf.beat_scheduler = "django_celery_beat.schedulers:DatabaseScheduler"
|
|||||||
|
|
||||||
# Configure the periodic tasks
|
# Configure the periodic tasks
|
||||||
app.conf.beat_schedule = {
|
app.conf.beat_schedule = {
|
||||||
"users-cleansing": { # Cleans abandoned users every 6 hours
|
"users-cleansing": { # Cleans abandoned users at midnight
|
||||||
"task": "users_cleansing",
|
"task": "users_cleansing",
|
||||||
"schedule": timedelta(hours=6),
|
"schedule": crontab(hour=0, minute=0),
|
||||||
|
},
|
||||||
|
"give-rewards": { # Referral rewards go from 'pending' to 'earned' at midnight
|
||||||
|
"task": "give_rewards",
|
||||||
|
"schedule": crontab(hour=0, minute=0),
|
||||||
},
|
},
|
||||||
"cache-market-prices": { # Cache market prices every minutes for now.
|
"cache-market-prices": { # Cache market prices every minutes for now.
|
||||||
"task": "cache_external_market_prices",
|
"task": "cache_external_market_prices",
|
||||||
|
Loading…
Reference in New Issue
Block a user