From ab32f76deb3c71b1be863bd7e7c0762811a79ccf Mon Sep 17 00:00:00 2001
From: guoshijiang <guoshijiang@192.168.0.106>
Date: Sun, 3 Mar 2024 01:08:02 +0800
Subject: [PATCH 1/3] airdrop grpc server

---
 airdrop/__init__.py                     |  0
 airdrop/admin.py                        |  3 ++
 airdrop/apps.py                         |  6 +++
 airdrop/migrations/__init__.py          |  0
 airdrop/models.py                       | 57 +++++++++++++++++++++
 airdrop/tests.py                        |  3 ++
 airdrop/views.py                        |  3 ++
 api/airdrop/__init__.py                 |  0
 api/airdrop/api_v1.py                   |  0
 hailstone/__init__.py                   |  4 --
 hailstone/settings.py                   |  1 +
 services/savour_rpc/airdrop_pb2.py      | 32 ++++++++++++
 services/savour_rpc/airdrop_pb2_grpc.py | 66 +++++++++++++++++++++++++
 13 files changed, 171 insertions(+), 4 deletions(-)
 create mode 100644 airdrop/__init__.py
 create mode 100644 airdrop/admin.py
 create mode 100644 airdrop/apps.py
 create mode 100644 airdrop/migrations/__init__.py
 create mode 100644 airdrop/models.py
 create mode 100644 airdrop/tests.py
 create mode 100644 airdrop/views.py
 create mode 100644 api/airdrop/__init__.py
 create mode 100644 api/airdrop/api_v1.py
 create mode 100644 services/savour_rpc/airdrop_pb2.py
 create mode 100644 services/savour_rpc/airdrop_pb2_grpc.py

diff --git a/airdrop/__init__.py b/airdrop/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/airdrop/admin.py b/airdrop/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/airdrop/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/airdrop/apps.py b/airdrop/apps.py
new file mode 100644
index 0000000..06e8113
--- /dev/null
+++ b/airdrop/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AirdropConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'airdrop'
diff --git a/airdrop/migrations/__init__.py b/airdrop/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/airdrop/models.py b/airdrop/models.py
new file mode 100644
index 0000000..178ecc9
--- /dev/null
+++ b/airdrop/models.py
@@ -0,0 +1,57 @@
+from django.db import models
+from common.models import BaseModel, Asset
+
+
+TypeChoice = [(x, x) for x in ['BridgeTransfer', 'BridgeStaking']]
+
+
+class User(BaseModel):
+    name = models.CharField(
+        max_length=100,
+        unique=True,
+        verbose_name='用户名'
+    )
+    photo = models.ImageField(
+        upload_to='symbol/%Y/%m/%d/',
+        blank=True,
+        null=True
+    )
+    type = models.CharField(
+        max_length=100,
+        choices=TypeChoice,
+        default="BridgeTransfer",
+        verbose_name='交易类别'
+    )
+    address = models.CharField(
+        max_length=100,
+        unique=True,
+        verbose_name='用户地址'
+    )
+
+    class Meta:
+        verbose_name = 'User'
+        verbose_name_plural = verbose_name
+
+    def __str__(self):
+        return self.name
+
+    def as_dict(self):
+        return {
+            'id': self.id,
+            'name': self.name,
+        }
+
+
+class PointsRecord(BaseModel):
+    address = models.CharField(
+        max_length=100,
+        unique=True,
+        verbose_name='用户地址'
+    )
+
+    class Meta:
+        verbose_name = 'PointsRecord'
+        verbose_name_plural = verbose_name
+
+    def __str__(self):
+        return self.address
diff --git a/airdrop/tests.py b/airdrop/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/airdrop/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/airdrop/views.py b/airdrop/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/airdrop/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/api/airdrop/__init__.py b/api/airdrop/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/airdrop/api_v1.py b/api/airdrop/api_v1.py
new file mode 100644
index 0000000..e69de29
diff --git a/hailstone/__init__.py b/hailstone/__init__.py
index 650b67a..e69de29 100644
--- a/hailstone/__init__.py
+++ b/hailstone/__init__.py
@@ -1,4 +0,0 @@
-import pymysql
-
-pymysql.version_info = (1, 4, 2, "final", 0)
-pymysql.install_as_MySQLdb()
\ No newline at end of file
diff --git a/hailstone/settings.py b/hailstone/settings.py
index 0000f81..9f47c6c 100644
--- a/hailstone/settings.py
+++ b/hailstone/settings.py
@@ -22,6 +22,7 @@
     "wallet",
     "services",
     "channels",
+    'airdrop',
 ]
 
 MIDDLEWARE = [
diff --git a/services/savour_rpc/airdrop_pb2.py b/services/savour_rpc/airdrop_pb2.py
new file mode 100644
index 0000000..16ccfa4
--- /dev/null
+++ b/services/savour_rpc/airdrop_pb2.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: savourrpc/airdrop.proto
+# Protobuf Python Version: 4.25.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from services.savour_rpc import common_pb2 as savourrpc_dot_common__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17savourrpc/airdrop.proto\x12\x11savourrpc.airdrop\x1a\x16savourrpc/common.proto\"Y\n\x10\x44ppLinkPointsReq\x12\x16\n\x0e\x63onsumer_token\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\x05\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\t\x12\x0e\n\x06points\x18\x04 \x01(\r\"D\n\x10\x44ppLinkPointsRep\x12#\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x15.savourrpc.ReturnCode\x12\x0b\n\x03msg\x18\x02 \x01(\t2s\n\x0e\x41irdropService\x12\x61\n\x13submitDppLinkPoints\x12#.savourrpc.airdrop.DppLinkPointsReq\x1a#.savourrpc.airdrop.DppLinkPointsRep\"\x00\x42\'\n\x14group.savour.airdropZ\x0f./proto/airdropb\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'savourrpc.airdrop_pb2', _globals)
+if _descriptor._USE_C_DESCRIPTORS == False:
+  _globals['DESCRIPTOR']._options = None
+  _globals['DESCRIPTOR']._serialized_options = b'\n\024group.savour.airdropZ\017./proto/airdrop'
+  _globals['_DPPLINKPOINTSREQ']._serialized_start=70
+  _globals['_DPPLINKPOINTSREQ']._serialized_end=159
+  _globals['_DPPLINKPOINTSREP']._serialized_start=161
+  _globals['_DPPLINKPOINTSREP']._serialized_end=229
+  _globals['_AIRDROPSERVICE']._serialized_start=231
+  _globals['_AIRDROPSERVICE']._serialized_end=346
+# @@protoc_insertion_point(module_scope)
diff --git a/services/savour_rpc/airdrop_pb2_grpc.py b/services/savour_rpc/airdrop_pb2_grpc.py
new file mode 100644
index 0000000..c5e5a9b
--- /dev/null
+++ b/services/savour_rpc/airdrop_pb2_grpc.py
@@ -0,0 +1,66 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+
+from services.savour_rpc import airdrop_pb2 as savourrpc_dot_airdrop__pb2
+
+
+class AirdropServiceStub(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def __init__(self, channel):
+        """Constructor.
+
+        Args:
+            channel: A grpc.Channel.
+        """
+        self.submitDppLinkPoints = channel.unary_unary(
+                '/savourrpc.airdrop.AirdropService/submitDppLinkPoints',
+                request_serializer=savourrpc_dot_airdrop__pb2.DppLinkPointsReq.SerializeToString,
+                response_deserializer=savourrpc_dot_airdrop__pb2.DppLinkPointsRep.FromString,
+                )
+
+
+class AirdropServiceServicer(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def submitDppLinkPoints(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+
+def add_AirdropServiceServicer_to_server(servicer, server):
+    rpc_method_handlers = {
+            'submitDppLinkPoints': grpc.unary_unary_rpc_method_handler(
+                    servicer.submitDppLinkPoints,
+                    request_deserializer=savourrpc_dot_airdrop__pb2.DppLinkPointsReq.FromString,
+                    response_serializer=savourrpc_dot_airdrop__pb2.DppLinkPointsRep.SerializeToString,
+            ),
+    }
+    generic_handler = grpc.method_handlers_generic_handler(
+            'savourrpc.airdrop.AirdropService', rpc_method_handlers)
+    server.add_generic_rpc_handlers((generic_handler,))
+
+
+ # This class is part of an EXPERIMENTAL API.
+class AirdropService(object):
+    """Missing associated documentation comment in .proto file."""
+
+    @staticmethod
+    def submitDppLinkPoints(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/savourrpc.airdrop.AirdropService/submitDppLinkPoints',
+            savourrpc_dot_airdrop__pb2.DppLinkPointsReq.SerializeToString,
+            savourrpc_dot_airdrop__pb2.DppLinkPointsRep.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

From a2964b3452e5b87fc80777fae1aac132353e87ce Mon Sep 17 00:00:00 2001
From: guoshijiang <guoshijiang@guoshijiangdeMacBook-Pro.local>
Date: Mon, 4 Mar 2024 11:27:06 +0800
Subject: [PATCH 2/3] airdrop grpc server development

---
 airdrop/admin.py                              | 14 +++-
 airdrop/airdrop_server.py                     | 36 +++++++++
 airdrop/management/__init__.py                |  0
 airdrop/management/commands/__init__.py       |  0
 airdrop/management/commands/airdrop_server.py | 20 +++++
 airdrop/migrations/0001_initial.py            | 54 +++++++++++++
 airdrop/models.py                             | 79 +++++++++++++++++--
 api/airdrop/api.sh                            |  4 +
 api/airdrop/api_v1.py                         | 46 +++++++++++
 api/urls.py                                   |  7 ++
 10 files changed, 252 insertions(+), 8 deletions(-)
 create mode 100644 airdrop/airdrop_server.py
 create mode 100644 airdrop/management/__init__.py
 create mode 100644 airdrop/management/commands/__init__.py
 create mode 100644 airdrop/management/commands/airdrop_server.py
 create mode 100644 airdrop/migrations/0001_initial.py
 create mode 100644 api/airdrop/api.sh

diff --git a/airdrop/admin.py b/airdrop/admin.py
index 8c38f3f..0a019ff 100644
--- a/airdrop/admin.py
+++ b/airdrop/admin.py
@@ -1,3 +1,15 @@
+#encoding=utf-8
+
 from django.contrib import admin
+from airdrop.models import (
+    AirdropUser,
+    PointsRecord
+)
+
+@admin.register(AirdropUser)
+class AddressAmountStatAdmin(admin.ModelAdmin):
+    list_display = ('id', 'name', 'address', 'points')
 
-# Register your models here.
+@admin.register(PointsRecord)
+class ChainAdmin(admin.ModelAdmin):
+    list_display = ('id', 'address', 'type', 'points')
diff --git a/airdrop/airdrop_server.py b/airdrop/airdrop_server.py
new file mode 100644
index 0000000..7c6684f
--- /dev/null
+++ b/airdrop/airdrop_server.py
@@ -0,0 +1,36 @@
+#encoding=utf-8
+
+import pytz
+from airdrop.models import AirdropUser, PointsRecord
+from services.savour_rpc import airdrop_pb2_grpc, common_pb2, airdrop_pb2
+from django.conf import settings
+
+tz = pytz.timezone(settings.TIME_ZONE)
+
+class AirdropServer(airdrop_pb2_grpc.AirdropServiceServicer):
+    def submitDppLinkPoints(self, request, context) -> airdrop_pb2.DppLinkPointsResponse:
+        airdrop_user: AirdropUser
+        type = str(request.type)
+        address = str(request.address)
+        points = int(request.points)
+        airdrop_tmp_user = AirdropUser.objects.filter(address=address).first()
+        if airdrop_tmp_user is not None:
+            airdrop_tmp_user.points += points
+            airdrop_tmp_user.save()
+            airdrop_user = airdrop_tmp_user
+        else:
+            airdrop_user = AirdropUser.objects.create(
+                address=address,
+                points=points
+            )
+        PointsRecord.objects.create(
+            user=airdrop_user,
+            address=address,
+            type=type,
+            points=points
+        )
+        return airdrop_pb2.DppLinkPointsResponse(
+            code=common_pb2.SUCCESS,
+            msg="submit dapplink points success",
+        )
+
diff --git a/airdrop/management/__init__.py b/airdrop/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/airdrop/management/commands/__init__.py b/airdrop/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/airdrop/management/commands/airdrop_server.py b/airdrop/management/commands/airdrop_server.py
new file mode 100644
index 0000000..db1eb43
--- /dev/null
+++ b/airdrop/management/commands/airdrop_server.py
@@ -0,0 +1,20 @@
+#encoding=utf-8
+
+import grpc
+from django.core.management.base import BaseCommand
+from concurrent import futures
+from services.savour_rpc import airdrop_pb2_grpc
+from airdrop.airdrop_server import AirdropServer
+
+
+class Command(BaseCommand):
+    def handle(self, *args, **options):
+        server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+        airdrop_pb2_grpc.add_AirdropServiceServicer_to_server(
+            AirdropServer(),
+            server
+        )
+        server.add_insecure_port('[::]:50251')
+        server.start()
+        print("airdrop rpc server start")
+        server.wait_for_termination()
\ No newline at end of file
diff --git a/airdrop/migrations/0001_initial.py b/airdrop/migrations/0001_initial.py
new file mode 100644
index 0000000..f3f8315
--- /dev/null
+++ b/airdrop/migrations/0001_initial.py
@@ -0,0 +1,54 @@
+# Generated by Django 4.1.1 on 2024-03-03 03:11
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AirdropUser',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('uuid', models.CharField(blank=True, max_length=100, null=True, unique=True)),
+                ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
+                ('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
+                ('name', models.CharField(default='unknown', max_length=100, unique=True, verbose_name='用户名')),
+                ('photo', models.ImageField(blank=True, null=True, upload_to='symbol/%Y/%m/%d/')),
+                ('address', models.CharField(max_length=100, unique=True, verbose_name='用户地址')),
+                ('email', models.EmailField(blank=True, max_length=254, null=True)),
+                ('points', models.PositiveIntegerField(default=0, verbose_name='积分数量')),
+                ('x_twitter', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='x')),
+                ('discord', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='discord')),
+                ('telegram', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='discord')),
+                ('info', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='个人介绍')),
+            ],
+            options={
+                'verbose_name': 'User',
+                'verbose_name_plural': 'User',
+            },
+        ),
+        migrations.CreateModel(
+            name='PointsRecord',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('uuid', models.CharField(blank=True, max_length=100, null=True, unique=True)),
+                ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
+                ('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
+                ('address', models.CharField(max_length=100, unique=True, verbose_name='用户地址')),
+                ('type', models.CharField(choices=[('BridgeTransfer', 'BridgeTransfer'), ('BridgeStaking', 'BridgeStaking')], default='BridgeTransfer', max_length=100, verbose_name='交易类别')),
+                ('points', models.PositiveIntegerField(default=0, verbose_name='积分数量')),
+                ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='airdrop_user', to='airdrop.airdropuser', verbose_name='收藏的商家')),
+            ],
+            options={
+                'verbose_name': 'PointsRecord',
+                'verbose_name_plural': 'PointsRecord',
+            },
+        ),
+    ]
diff --git a/airdrop/models.py b/airdrop/models.py
index 178ecc9..5f86ca7 100644
--- a/airdrop/models.py
+++ b/airdrop/models.py
@@ -5,8 +5,9 @@
 TypeChoice = [(x, x) for x in ['BridgeTransfer', 'BridgeStaking']]
 
 
-class User(BaseModel):
+class AirdropUser(BaseModel):
     name = models.CharField(
+        default="unknown",
         max_length=100,
         unique=True,
         verbose_name='用户名'
@@ -16,17 +17,47 @@ class User(BaseModel):
         blank=True,
         null=True
     )
-    type = models.CharField(
-        max_length=100,
-        choices=TypeChoice,
-        default="BridgeTransfer",
-        verbose_name='交易类别'
-    )
     address = models.CharField(
         max_length=100,
         unique=True,
         verbose_name='用户地址'
     )
+    email = models.EmailField(
+        blank=True,
+        null=True
+    )
+    points = models.PositiveIntegerField(
+        default=0,
+        verbose_name="积分数量"
+    )
+    x_twitter = models.CharField(
+        max_length=100,
+        default="",
+        blank=True,
+        null=True,
+        verbose_name="x",
+    )
+    discord = models.CharField(
+        max_length=100,
+        default="",
+        blank=True,
+        null=True,
+        verbose_name="discord",
+    )
+    telegram = models.CharField(
+        max_length=100,
+        default="",
+        blank=True,
+        null=True,
+        verbose_name="discord",
+    )
+    info = models.CharField(
+        max_length=100,
+        default="",
+        blank=True,
+        null=True,
+        verbose_name="个人介绍",
+    )
 
     class Meta:
         verbose_name = 'User'
@@ -39,15 +70,41 @@ def as_dict(self):
         return {
             'id': self.id,
             'name': self.name,
+            'photo': str(self.photo),
+            'address': self.address,
+            'email': self.email,
+            'points': self.points,
+            'x_twitter': self.x_twitter,
+            'discord': self.discord,
+            'telegram': self.telegram,
+            'info': self.info
         }
 
 
 class PointsRecord(BaseModel):
+    user = models.ForeignKey(
+        AirdropUser,
+        related_name="airdrop_user",
+        on_delete=models.CASCADE,
+        blank=True,
+        null=True,
+        verbose_name="收藏的商家",
+    )
     address = models.CharField(
         max_length=100,
         unique=True,
         verbose_name='用户地址'
     )
+    type = models.CharField(
+        max_length=100,
+        choices=TypeChoice,
+        default="BridgeTransfer",
+        verbose_name='交易类别'
+    )
+    points = models.PositiveIntegerField(
+        default=0,
+        verbose_name="积分数量"
+    )
 
     class Meta:
         verbose_name = 'PointsRecord'
@@ -55,3 +112,11 @@ class Meta:
 
     def __str__(self):
         return self.address
+
+    def as_dict(self):
+        return {
+            'id': self.id,
+            'name': self.address,
+            'type': self.type,
+            'points': self.points
+        }
diff --git a/api/airdrop/api.sh b/api/airdrop/api.sh
new file mode 100644
index 0000000..72d49bc
--- /dev/null
+++ b/api/airdrop/api.sh
@@ -0,0 +1,4 @@
+curl --location --request POST 'http://127.0.0.1:8000/api/get_points_by_address' --header 'Content-Type: application/json' --data-raw '{"address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0" }'
+
+
+curl --location --request POST 'http://127.0.0.1:8000/api/get_points_record_by_address' --header 'Content-Type: application/json' --data-raw '{"address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0" }'
\ No newline at end of file
diff --git a/api/airdrop/api_v1.py b/api/airdrop/api_v1.py
index e69de29..b88d372 100644
--- a/api/airdrop/api_v1.py
+++ b/api/airdrop/api_v1.py
@@ -0,0 +1,46 @@
+#encoding=utf-8
+
+import json
+from common.helpers import (
+    ok_json,
+    error_json
+)
+from airdrop.models import (
+   AirdropUser,
+   PointsRecord
+)
+
+# @check_api_token
+def get_points_by_address(request):
+    params = json.loads(request.body.decode())
+    address = params.get("address", None)
+    if address is None:
+        return error_json("address is empty", 4000)
+    airdrop_user = AirdropUser.objects.filter(address=address).first()
+    if airdrop_user is not None:
+        return ok_json(airdrop_user.as_dict())
+    else:
+        return error_json("No this user address points", 4000)
+
+# @check_api_token
+def get_points_record_by_address(request):
+    params = json.loads(request.body.decode())
+    address = params.get("address", None)
+    if address is None:
+        return error_json("address is empty", 4000)
+    page = params.get('page', 1)
+    page_size = params.get('page_size', 20)
+    start = (page - 1) * page_size
+    end = start + page_size
+    points = PointsRecord.objects.filter(address=address).order_by("-id")[start:end]
+    total = PointsRecord.objects.filter(address=address).order_by("-id").count()
+    point_list = []
+    for point in points:
+        point_list.append(point.as_dict())
+    data = {
+        "total": total,
+        "points": point_list,
+    }
+    return ok_json(data)
+
+
diff --git a/api/urls.py b/api/urls.py
index 5bfbdf7..8d73114 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -52,6 +52,10 @@
     get_arcticle_detail,
     like_article
 )
+from api.airdrop.api_v1 import (
+    get_points_by_address,
+    get_points_record_by_address,
+)
 
 urlpatterns = [
     # Hd wallet module
@@ -100,4 +104,7 @@
     path(r'set_recovery_key', set_recovery_key, name='set_recovery_key'),
     path(r'get_tokens', get_tokens, name='get_tokens'),
 
+    # airdrop
+    path(r'get_points_by_address', get_points_by_address, name='get_points_by_address'),
+    path(r'get_points_record_by_address', get_points_record_by_address, name='get_points_record_by_address'),
 ]
\ No newline at end of file

From 41e7ae17dec7be7f592fc08409888a23578407c9 Mon Sep 17 00:00:00 2001
From: guoshijiang <guoshijiang@192.168.0.103>
Date: Wed, 13 Mar 2024 13:30:05 +0800
Subject: [PATCH 3/3] finish airdrop api development

---
 airdrop/migrations/0001_initial.py |  10 +--
 airdrop/models.py                  |  18 ++++-
 airdrop_api.md                     | 111 +++++++++++++++++++++++++++++
 api/airdrop/api.sh                 |   4 --
 api/airdrop/api_v1.py              |  55 ++++++++++++--
 api/urls.py                        |   4 ++
 6 files changed, 186 insertions(+), 16 deletions(-)
 create mode 100644 airdrop_api.md
 delete mode 100644 api/airdrop/api.sh

diff --git a/airdrop/migrations/0001_initial.py b/airdrop/migrations/0001_initial.py
index f3f8315..4217d5f 100644
--- a/airdrop/migrations/0001_initial.py
+++ b/airdrop/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.1.1 on 2024-03-03 03:11
+# Generated by Django 4.1.1 on 2024-03-13 05:29
 
 from django.db import migrations, models
 import django.db.models.deletion
@@ -19,7 +19,9 @@ class Migration(migrations.Migration):
                 ('uuid', models.CharField(blank=True, max_length=100, null=True, unique=True)),
                 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
                 ('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
-                ('name', models.CharField(default='unknown', max_length=100, unique=True, verbose_name='用户名')),
+                ('name', models.CharField(default='unknown', max_length=100, verbose_name='用户名')),
+                ('invite_code', models.CharField(default='0000-0000-0000', max_length=100, unique=True, verbose_name='邀请码')),
+                ('invite_me_uuid', models.CharField(default='0000-0000-0000', max_length=100, verbose_name='邀请人')),
                 ('photo', models.ImageField(blank=True, null=True, upload_to='symbol/%Y/%m/%d/')),
                 ('address', models.CharField(max_length=100, unique=True, verbose_name='用户地址')),
                 ('email', models.EmailField(blank=True, max_length=254, null=True)),
@@ -41,8 +43,8 @@ class Migration(migrations.Migration):
                 ('uuid', models.CharField(blank=True, max_length=100, null=True, unique=True)),
                 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
                 ('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
-                ('address', models.CharField(max_length=100, unique=True, verbose_name='用户地址')),
-                ('type', models.CharField(choices=[('BridgeTransfer', 'BridgeTransfer'), ('BridgeStaking', 'BridgeStaking')], default='BridgeTransfer', max_length=100, verbose_name='交易类别')),
+                ('address', models.CharField(max_length=100, verbose_name='用户地址')),
+                ('type', models.CharField(choices=[('Invite', 'Invite'), ('BridgeTransfer', 'BridgeTransfer'), ('BridgeStaking', 'BridgeStaking')], default='BridgeTransfer', max_length=100, verbose_name='交易类别')),
                 ('points', models.PositiveIntegerField(default=0, verbose_name='积分数量')),
                 ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='airdrop_user', to='airdrop.airdropuser', verbose_name='收藏的商家')),
             ],
diff --git a/airdrop/models.py b/airdrop/models.py
index 5f86ca7..b26a372 100644
--- a/airdrop/models.py
+++ b/airdrop/models.py
@@ -2,16 +2,28 @@
 from common.models import BaseModel, Asset
 
 
-TypeChoice = [(x, x) for x in ['BridgeTransfer', 'BridgeStaking']]
+TypeChoice = [(x, x) for x in ['Invite', 'BridgeTransfer', 'BridgeStaking']]
 
 
 class AirdropUser(BaseModel):
     name = models.CharField(
         default="unknown",
         max_length=100,
-        unique=True,
+        unique=False,
         verbose_name='用户名'
     )
+    invite_code = models.CharField(
+        default="0000-0000-0000",
+        max_length=100,
+        unique=True,
+        verbose_name='邀请码'
+    )
+    invite_me_uuid = models.CharField(
+        default="0000-0000-0000",
+        max_length=100,
+        unique=False,
+        verbose_name='邀请人'
+    )
     photo = models.ImageField(
         upload_to='symbol/%Y/%m/%d/',
         blank=True,
@@ -92,7 +104,7 @@ class PointsRecord(BaseModel):
     )
     address = models.CharField(
         max_length=100,
-        unique=True,
+        unique=False,
         verbose_name='用户地址'
     )
     type = models.CharField(
diff --git a/airdrop_api.md b/airdrop_api.md
new file mode 100644
index 0000000..ce6d3d2
--- /dev/null
+++ b/airdrop_api.md
@@ -0,0 +1,111 @@
+# 空投接口
+
+## 1. 获取邀请码
+
+接口请求
+```
+curl --location 'http://127.0.0.1:8000/api/get_invite_code_by_address' \
+--header 'Content-Type: application/json' \
+--data '{
+    "address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0"
+}'
+```
+返回值
+
+```
+{
+    "ok": true,
+    "code": 200,
+    "result": {
+        "invite_code": "0000-0000-0000"
+    }
+}
+```
+
+
+## 2. 提交邀请信息
+
+接口请求
+```
+curl --location 'http://127.0.0.1:8000/api/submit_invite_info' \
+--header 'Content-Type: application/json' \
+--data '{
+    "invite_code": "0000-0000-0000",
+    "address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F1"
+}'
+```
+返回值
+
+```
+{
+    "ok": true,
+    "code": 200,
+    "result": {}
+}
+```
+
+
+## 3. 获取个人信息和积分
+
+接口请求
+```
+curl --location 'http://127.0.0.1:8000/api/get_points_by_address' \
+--header 'Content-Type: application/json' \
+--data '{
+    "address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F1"
+}'
+```
+返回值
+
+```
+{
+    "ok": true,
+    "code": 200,
+    "result": {
+        "id": 5,
+        "name": "unknown",
+        "photo": "",
+        "address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F1",
+        "email": null,
+        "points": 0,       
+        "x_twitter": "",
+        "discord": "",
+        "telegram": "",
+        "info": ""
+    }
+}
+```
+
+
+## 4. 根据地址获取积分记录
+
+接口请求
+```
+curl --location 'http://127.0.0.1:8000/api/get_points_record_by_address' \
+--header 'Content-Type: application/json' \
+--data '{
+    "address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0"
+}'
+```
+返回值
+
+```
+{
+    "ok": true,
+    "code": 200,
+    "result": {
+        "total": 1,
+        "points": [
+            {
+                "id": 1,
+                "name": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0",
+                "type": "BridgeTransfer",
+                "points": 1
+            }
+        ]
+    }
+}
+```
+
+
+
diff --git a/api/airdrop/api.sh b/api/airdrop/api.sh
deleted file mode 100644
index 72d49bc..0000000
--- a/api/airdrop/api.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-curl --location --request POST 'http://127.0.0.1:8000/api/get_points_by_address' --header 'Content-Type: application/json' --data-raw '{"address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0" }'
-
-
-curl --location --request POST 'http://127.0.0.1:8000/api/get_points_record_by_address' --header 'Content-Type: application/json' --data-raw '{"address": "0xe3b4ECd2EC88026F84cF17fef8bABfD9184C94F0" }'
\ No newline at end of file
diff --git a/api/airdrop/api_v1.py b/api/airdrop/api_v1.py
index b88d372..3bc77b5 100644
--- a/api/airdrop/api_v1.py
+++ b/api/airdrop/api_v1.py
@@ -1,15 +1,61 @@
-#encoding=utf-8
+# encoding=utf-8
 
 import json
+import uuid
+
 from common.helpers import (
     ok_json,
     error_json
 )
 from airdrop.models import (
-   AirdropUser,
-   PointsRecord
+    AirdropUser,
+    PointsRecord
 )
 
+
+# @check_api_token
+def get_invite_code_by_address(request):
+    params = json.loads(request.body.decode())
+    address = params.get("address", None)
+    if address is not None:
+        airdrop_user = AirdropUser.objects.filter(address=address).first()
+        if airdrop_user is not None:
+            data = {
+                "invite_code": airdrop_user.invite_code,
+            }
+            return ok_json(data)
+        else:
+            return error_json("address is not exist", 4000)
+    else:
+        return error_json("address is none", 4000)
+
+
+# @check_api_token
+def submit_invite_info(request):
+    params = json.loads(request.body.decode())
+    address = params.get("address", None)
+    invite_code = params.get("invite_code", None)
+    if address is None or invite_code is None:
+        return error_json("address or invite_code params is empty", 4000)
+    invite_user = AirdropUser.objects.filter(invite_code=invite_code).first()
+    if invite_user is None:
+        return error_json("This user is not exist", 4000)
+    AirdropUser.objects.create(
+        invite_code=uuid.uuid4(),
+        invite_me_uuid=invite_user.uuid,
+        address=address
+    )
+    if invite_user.points < 10:
+        invite_user.points = invite_user.points + 2
+        invite_user.save()
+        PointsRecord.objects.create(
+            user=invite_user,
+            address=invite_user.address,
+            type='Invite',
+            points=2
+        )
+    return ok_json({})
+
 # @check_api_token
 def get_points_by_address(request):
     params = json.loads(request.body.decode())
@@ -22,6 +68,7 @@ def get_points_by_address(request):
     else:
         return error_json("No this user address points", 4000)
 
+
 # @check_api_token
 def get_points_record_by_address(request):
     params = json.loads(request.body.decode())
@@ -42,5 +89,3 @@ def get_points_record_by_address(request):
         "points": point_list,
     }
     return ok_json(data)
-
-
diff --git a/api/urls.py b/api/urls.py
index 8d73114..0e9293d 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -55,6 +55,8 @@
 from api.airdrop.api_v1 import (
     get_points_by_address,
     get_points_record_by_address,
+    get_invite_code_by_address,
+    submit_invite_info
 )
 
 urlpatterns = [
@@ -105,6 +107,8 @@
     path(r'get_tokens', get_tokens, name='get_tokens'),
 
     # airdrop
+    path(r'get_invite_code_by_address', get_invite_code_by_address, name='get_invite_code_by_address'),
+    path(r'submit_invite_info', submit_invite_info, name='submit_invite_info'),
     path(r'get_points_by_address', get_points_by_address, name='get_points_by_address'),
     path(r'get_points_record_by_address', get_points_record_by_address, name='get_points_record_by_address'),
 ]
\ No newline at end of file