mirror of
https://github.com/more-tech4-magnum-opus/backend.git
synced 2024-11-24 12:23:44 +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
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
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
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from ..services import create_season
|
from ..services import create_season
|
||||||
from users.models import User, Clan
|
from users.models import User, Department, Stream, Command, Clan
|
||||||
from users.models import User, Department, Stream, Command
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
@ -19,12 +18,14 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
"command",
|
"command",
|
||||||
"department",
|
"department",
|
||||||
"clan_name",
|
"clan_name",
|
||||||
|
"money",
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"password": {"write_only": True},
|
"password": {"write_only": True},
|
||||||
"wallet_public_key": {"read_only": True},
|
"wallet_public_key": {"read_only": True},
|
||||||
"clan_name": {"read_only": True},
|
"clan_name": {"read_only": True},
|
||||||
"department": {"read_only": True},
|
"department": {"read_only": True},
|
||||||
|
"money": {"read_only": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
@ -41,6 +42,9 @@ class CreateSeasonSerializer(serializers.Serializer):
|
||||||
create_season()
|
create_season()
|
||||||
return {"created": True}
|
return {"created": True}
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CommandSerializer(serializers.ModelSerializer):
|
class CommandSerializer(serializers.ModelSerializer):
|
||||||
workers = UserSerializer(many=True)
|
workers = UserSerializer(many=True)
|
||||||
|
|
|
@ -10,110 +10,6 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
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()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ class User(AbstractUser):
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=6, choices=WorkerType.choices, default=WorkerType.WORKER
|
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(
|
command = models.ForeignKey(
|
||||||
"users.Command", related_name="workers", on_delete=models.CASCADE
|
"users.Command", related_name="workers", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
@ -30,6 +32,8 @@ class User(AbstractUser):
|
||||||
respect = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
respect = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
||||||
wallet_private_key = models.CharField(max_length=96, unique=True)
|
wallet_private_key = models.CharField(max_length=96, unique=True)
|
||||||
wallet_public_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)
|
telegram = models.CharField(max_length=100, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -2,8 +2,8 @@ from random import shuffle
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from blockchain.services import transact_from_admin
|
||||||
from .models import Clan, User
|
from .models import Clan, User
|
||||||
from utils.blockchain import transfer_rubbles
|
|
||||||
import requests as r
|
import requests as r
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@ def end_season():
|
||||||
mx_value = sum(map(lambda user: user.respect, clan.users.all()))
|
mx_value = sum(map(lambda user: user.respect, clan.users.all()))
|
||||||
mx_clan = clan
|
mx_clan = clan
|
||||||
for user in mx_clan.users.all():
|
for user in mx_clan.users.all():
|
||||||
transfer_rubbles(
|
transact_from_admin(user, 100)
|
||||||
settings.MAIN_WALLET,
|
|
||||||
user.wallet_public_key,
|
|
||||||
100,
|
|
||||||
)
|
|
||||||
Clan.objects.all().delete()
|
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):
|
def create_chat(clan: Clan):
|
||||||
user_list = list(map(lambda user: user.telegram, clan.users.all()))
|
user_list = list(map(lambda user: user.telegram, clan.users.all()))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user