mirror of
https://github.com/more-tech4-magnum-opus/backend.git
synced 2024-11-27 21:43:43 +03:00
added blockchain operations, transaction history, minor optimisations
This commit is contained in:
parent
12f81dbf3f
commit
79587c322d
0
app/blockchain/__init__.py
Normal file
0
app/blockchain/__init__.py
Normal file
3
app/blockchain/admin.py
Normal file
3
app/blockchain/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
app/blockchain/apps.py
Normal file
6
app/blockchain/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BlockchainConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'blockchain'
|
15
app/blockchain/migrations/0001_initial.py
Normal file
15
app/blockchain/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Generated by Django 4.0.8 on 2022-10-08 17:58
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = []
|
0
app/blockchain/migrations/__init__.py
Normal file
0
app/blockchain/migrations/__init__.py
Normal file
52
app/blockchain/models.py
Normal file
52
app/blockchain/models.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from users.models import User
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
user_from = models.ForeignKey(
|
||||
User, related_name="transactions_from", on_delete=models.CASCADE
|
||||
)
|
||||
user_to = models.ForeignKey(
|
||||
User, related_name="transactions_to", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
amount = models.IntegerField(validators=[MinValueValidator(1)])
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
hash = models.CharField(max_length=256, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"transaction from {self.user_from} to {self.user_to}"
|
||||
|
||||
|
||||
class AdminTransaction(models.Model):
|
||||
class TransactionType(models.TextChoices):
|
||||
FROM = "PAYMENT", "payment"
|
||||
TO = "SALARY", "salary"
|
||||
|
||||
type = models.CharField(max_length=7, choices=TransactionType.choices)
|
||||
amount = models.IntegerField(validators=[MinValueValidator(1)])
|
||||
user = models.ForeignKey(
|
||||
User, related_name="admin_transactions", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
hash = models.CharField(max_length=256, unique=True)
|
||||
|
||||
@property
|
||||
def user_from(self):
|
||||
return "system" if self.type == self.TransactionType.TO else self.user.username
|
||||
|
||||
@property
|
||||
def user_to(self):
|
||||
return (
|
||||
"system" if self.type == self.TransactionType.FROM else self.user.username
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.amount)
|
38
app/blockchain/services.py
Normal file
38
app/blockchain/services.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.conf import settings
|
||||
|
||||
from blockchain.models import Transaction, AdminTransaction
|
||||
from users.models import User
|
||||
from utils.blockchain import transfer_rubbles
|
||||
|
||||
|
||||
def list_user_transactions(user: User) -> list:
|
||||
transaction = []
|
||||
qs = (
|
||||
user.transactions_to.all()
|
||||
| user.transactions_from.all()
|
||||
| user.admin_transactions.all()
|
||||
)
|
||||
qs.order_by("created")
|
||||
username = user.username
|
||||
for el in qs: # type: Transaction
|
||||
transaction.append(
|
||||
{
|
||||
"type": "addition" if el.user_from.username == username else "transfer",
|
||||
"user_from": el.user_from.username,
|
||||
"user_to": el.user_to.username,
|
||||
"amount": el.amount,
|
||||
}
|
||||
)
|
||||
|
||||
return transaction
|
||||
|
||||
|
||||
def transact_from_admin(user: User, amount: int):
|
||||
priv_key = settings.MAIN_WALLET
|
||||
t = transfer_rubbles(priv_key, user.wallet_public_key, amount)
|
||||
AdminTransaction.objects.create(
|
||||
type="SALARY", amount=amount, user=user, hash=t.transaction_hash
|
||||
)
|
||||
user.money += amount
|
||||
user.save(update_fields=["money"])
|
||||
|
40
app/blockchain/signals.py
Normal file
40
app/blockchain/signals.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from blockchain.models import Transaction
|
||||
from utils.blockchain import get_balance, transfer_rubbles
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Transaction)
|
||||
def validate_create_transaction(sender, instance: Transaction, **kwargs):
|
||||
# validate transaction
|
||||
if instance.user_from == instance.user_to:
|
||||
raise ValidationError("Cannot transfer to yourself")
|
||||
|
||||
if instance.amount == 0:
|
||||
raise ValidationError("Cannot transfer 0 money")
|
||||
|
||||
# Potential Race condition, use transaction with atomics in prod
|
||||
user_from_money = int(get_balance(instance.user_from.wallet_public_key).coins)
|
||||
if instance.user_from.money != user_from_money:
|
||||
instance.user_from.money = user_from_money
|
||||
instance.user_from.save(update_fields=["money"])
|
||||
|
||||
if user_from_money - instance.amount <= 0:
|
||||
raise ValidationError(
|
||||
f"{instance.user_from.username} doesn't have enough money"
|
||||
)
|
||||
|
||||
transaction = transfer_rubbles(
|
||||
instance.user_from.wallet_private_key,
|
||||
instance.user_to.wallet_public_key,
|
||||
amount=instance.amount,
|
||||
)
|
||||
instance.user_from.money -= instance.amount
|
||||
instance.user_from.save(update_fields=["money"])
|
||||
|
||||
instance.user_to.money += instance.amount
|
||||
instance.user_to.save(update_fields=["money"])
|
||||
|
||||
instance.hash = transaction.transaction_hash
|
|
@ -9,37 +9,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('slug', models.SlugField(max_length=8)),
|
||||
('about', models.TextField(blank=True)),
|
||||
('starts', models.DateTimeField()),
|
||||
('image', models.ImageField(blank=True, upload_to='uploads/')),
|
||||
('image_cropped', models.ImageField(blank=True, upload_to='cropped/')),
|
||||
('planning', models.IntegerField(default=0)),
|
||||
('attended', models.IntegerField(default=0)),
|
||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events_created', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-starts'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventAttendance',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token', models.CharField(max_length=128, unique=True)),
|
||||
('attended', models.BooleanField(default=False)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='people', to='events.event')),
|
||||
('worker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
operations = []
|
||||
|
|
32
app/fixtures/departmens.json
Normal file
32
app/fixtures/departmens.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"model": "users.department",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Backend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.department",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Frontend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.stream",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Colloring buttons",
|
||||
"department": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.command",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Colloring main page button go get money",
|
||||
"stream": 1
|
||||
}
|
||||
}
|
||||
]
|
|
@ -9,22 +9,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('description', models.TextField()),
|
||||
('image', models.ImageField(upload_to='uploads/')),
|
||||
('image_cropped', models.ImageField(blank=True, upload_to='cropped/')),
|
||||
('nft', models.CharField(blank=True, max_length=500)),
|
||||
('price', models.IntegerField()),
|
||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from ..services import create_season
|
||||
from users.models import User, Clan
|
||||
from users.models import User, Department, Stream, Command
|
||||
from users.models import User, Department, Stream, Command, Clan
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
|
@ -19,12 +18,14 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
"command",
|
||||
"department",
|
||||
"clan_name",
|
||||
"money",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"password": {"write_only": True},
|
||||
"wallet_public_key": {"read_only": True},
|
||||
"clan_name": {"read_only": True},
|
||||
"department": {"read_only": True},
|
||||
"money": {"read_only": True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
|
@ -41,6 +42,9 @@ class CreateSeasonSerializer(serializers.Serializer):
|
|||
create_season()
|
||||
return {"created": True}
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
|
||||
class CommandSerializer(serializers.ModelSerializer):
|
||||
workers = UserSerializer(many=True)
|
||||
|
|
|
@ -10,110 +10,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="date joined"
|
||||
),
|
||||
),
|
||||
("about", models.TextField(blank=True)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["-id"],
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -22,7 +22,9 @@ class User(AbstractUser):
|
|||
type = models.CharField(
|
||||
max_length=6, choices=WorkerType.choices, default=WorkerType.WORKER
|
||||
)
|
||||
clan = models.ForeignKey("users.Clan", related_name="users", on_delete=models.SET_NULL, null=True)
|
||||
clan = models.ForeignKey(
|
||||
"users.Clan", related_name="users", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
command = models.ForeignKey(
|
||||
"users.Command", related_name="workers", on_delete=models.CASCADE
|
||||
)
|
||||
|
@ -30,6 +32,8 @@ class User(AbstractUser):
|
|||
respect = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
||||
wallet_private_key = models.CharField(max_length=96, unique=True)
|
||||
wallet_public_key = models.CharField(max_length=96, unique=True)
|
||||
money = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
||||
|
||||
telegram = models.CharField(max_length=100, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -2,8 +2,8 @@ from random import shuffle
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
from blockchain.services import transact_from_admin
|
||||
from .models import Clan, User
|
||||
from utils.blockchain import transfer_rubbles
|
||||
import requests as r
|
||||
import json
|
||||
|
||||
|
@ -16,13 +16,13 @@ def end_season():
|
|||
mx_value = sum(map(lambda user: user.respect, clan.users.all()))
|
||||
mx_clan = clan
|
||||
for user in mx_clan.users.all():
|
||||
transfer_rubbles(
|
||||
settings.MAIN_WALLET,
|
||||
user.wallet_public_key,
|
||||
100,
|
||||
)
|
||||
transact_from_admin(user, 100)
|
||||
Clan.objects.all().delete()
|
||||
|
||||
for user in User.objects.filter(type=User.WorkerType.WORKER):
|
||||
if user.salary > 0:
|
||||
transact_from_admin(user, user.salary)
|
||||
|
||||
|
||||
def create_chat(clan: Clan):
|
||||
user_list = list(map(lambda user: user.telegram, clan.users.all()))
|
||||
|
|
Loading…
Reference in New Issue
Block a user