Skip to content

Commit

Permalink
Merge pull request #138 from PotLock/testnet
Browse files Browse the repository at this point in the history
testnet -> Dev
  • Loading branch information
Prometheo authored Jan 2, 2025
2 parents d4669ec + 88c7bea commit fb90a57
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 39 deletions.
161 changes: 130 additions & 31 deletions pots/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
from rest_framework.response import Response
from rest_framework.views import APIView
import json
import requests
from django.core.cache import cache
from django.utils.functional import cached_property

from accounts.models import Account
from accounts.serializers import (
Expand All @@ -31,6 +34,7 @@
)
from .models import Pot, PotApplication, PotApplicationStatus, PotFactory
from .serializers import (
PAGINATED_MPDAO_USER_EXAMPLE,
PAGINATED_PAYOUT_EXAMPLE,
PAGINATED_POT_APPLICATION_EXAMPLE,
PAGINATED_POT_EXAMPLE,
Expand Down Expand Up @@ -346,65 +350,160 @@ def get(self, request: Request, *args, **kwargs):


class MpdaoUsers(APIView):
DEFAULT_PAGE_SIZE = 30

@extend_schema(
parameters=[
OpenApiParameter("voter_id", str, OpenApiParameter.QUERY, required=False, description="NEAR account ID of the voter"),
OpenApiParameter("page", int, OpenApiParameter.QUERY, required=False, description="Page number (starts from 1)"),
OpenApiParameter("page_size", int, OpenApiParameter.QUERY, required=False, description="Number of items per page (default: 30)"),
],
responses={
200: OpenApiResponse(
response=MpdaoVoterSerializer,
description="Returns voter details",
description="Returns voter details or paginated list of all voters",
examples=[
PAGINATED_MPDAO_USER_EXAMPLE
]
),
404: OpenApiResponse(description="Voter not found"),
500: OpenApiResponse(description="File read error"),
},
)
@method_decorator(cache_page(60 * 5))
@method_decorator(cache_page(14400))
def get(self, request: Request, *args, **kwargs):
voter_id = request.query_params.get("voter_id")

try:
# Read JSON file
json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json')
with open(json_path, 'r') as file:
all_voters = json.load(file)

# Find voter by ID in JSON
voter_data = next(
(voter for voter in all_voters if voter['voter_id'] == voter_id),
None
if voter_id:
return self.get_single_voter(voter_id)
else:
return self.get_all_voters(request.query_params)
except FileNotFoundError:
return Response(
{"message": "Voters snapshot file not found"},
status=500
)
except Exception as e:
return Response(
{"message": f"Error processing voter data: {str(e)}"},
status=500
)

# Check in the database
def get_all_voters(self, query_params):
"""Handle request for all voters with pagination"""
try:
unique_voters = self.get_unique_voters()

try:
account = Account.objects.get(id=voter_id)
account_data = AccountSerializer(account).data
except Account.DoesNotExist:
account_data = None

page = max(int(query_params.get('page', 1)), 1)
page_size = min(int(query_params.get('page_size', self.DEFAULT_PAGE_SIZE)), 100)
except ValueError:
page = 1
page_size = self.DEFAULT_PAGE_SIZE

# Calculate pagination slices
start_idx = (page - 1) * page_size
end_idx = start_idx + page_size

if not voter_data and not account_data:
return Response(
{"message": f"Voter with ID {voter_id} not found."},
status=404
)

# Get paginated voters
paginated_voters = unique_voters[start_idx:end_idx]

response_data = {
# Get all account data for the page in one query
accounts = self.get_bulk_account_data(paginated_voters)

voters_data = []
for voter_id in paginated_voters:
voter_data = self.get_voter_data(voter_id)
account_data = accounts.get(voter_id)

voters_data.append({
"voter_id": voter_id,
"account_data": account_data,
"voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data
}
})

base_url = self.request.build_absolute_uri().split('?')[0]

next_url = None
if end_idx < len(unique_voters):
next_params = query_params.copy()
next_params['page'] = page + 1
next_params['page_size'] = page_size
next_url = f"{base_url}?{'&'.join(f'{k}={v}' for k, v in next_params.items())}"

previous_url = None
if page > 1:
prev_params = query_params.copy()
prev_params['page'] = page - 1
prev_params['page_size'] = page_size
previous_url = f"{base_url}?{'&'.join(f'{k}={v}' for k, v in prev_params.items())}"

response_data = {
"count": len(paginated_voters),
"next": next_url,
"previous": previous_url,
"results": voters_data
}

return Response(response_data)

except FileNotFoundError:
return Response(
{"message": "Voters snapshot file not found"},
status=500
)
except Exception as e:
return Response(
{"message": f"Error processing voter data: {str(e)}"},
{"message": f"Error fetching voters: {str(e)}"},
status=500
)

@cached_property
def all_voters(self):
"""Cache the JSON file in memory"""
json_path = os.path.join(settings.BASE_DIR, 'pots', 'last-snapshot-AllVoters.json')
with open(json_path, 'r') as file:
return json.load(file)

def get_unique_voters(self):
"""Get and cache the unique voters from RPC"""
cache_key = 'mpdao_unique_voters'
cached_voters = cache.get(cache_key)

if cached_voters is not None:
return cached_voters

url = "https://rpc.web4.near.page/account/mpdao.vote.potlock.near/view/get_unique_voters?election_id.json=1"
response = requests.get(url)

if response.status_code != 200:
raise Exception("Error fetching voters from contract")

unique_voters = response.json()
cache.set(cache_key, unique_voters, 86400) # Cache for 24 hours
return unique_voters

def get_bulk_account_data(self, voter_ids):
"""Get account data for multiple voters in one query"""
accounts = {
account.id: AccountSerializer(account).data
for account in Account.objects.select_related().filter(id__in=voter_ids)
}
return accounts

def get_voter_data(self, voter_id):
"""Get voter data from cached JSON"""
return next(
(voter for voter in self.all_voters if voter['voter_id'] == voter_id),
None
)

def get_single_voter(self, voter_id):
"""Handle single voter request"""
voter_data = self.get_voter_data(voter_id)
accounts = self.get_bulk_account_data([voter_id])
account_data = accounts.get(voter_id)

response_data = {
"account_data": account_data,
"voter_data": MpdaoVoterSerializer(voter_data or {"voter_id": voter_id}).data
}

return Response(response_data)

37 changes: 29 additions & 8 deletions pots/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,35 @@ class PaginatedPotsResponseSerializer(serializers.Serializer):
previous = serializers.CharField(allow_null=True)
results = PotSerializer(many=True)

SIMPLE_MPDAO_VOTER_INFO_EXAMPLE = {
"voter_id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6",
"account_data": {
"id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6",
"total_donations_in_usd": 0.0,
"total_donations_out_usd": 0.0,
"total_matching_pool_allocations_usd": 0.0,
"donors_count": 0,
"near_social_profile_data": {
"name": "Manny98"
}
},
"voter_data": {
"voter_id": "01f7f1d124232a78b2a2f8e8ac21219d1ecd131ab592c2d9cae505d3c3cd21b6",
"balance_in_contract": None,
"voting_power": None,
"locking_positions": None,
"vote_positions": None,
"staking_token_balance": "0",
"staking_token_id": "meta-pool.near"
}
}

PAGINATED_MPDAO_USER_EXAMPLE = {
"count": 30,
"next": "http://127.0.0.1:8000/api/v1/mpdao/voter-info?page=3&page_size=30",
"previous": None,
"results": [SIMPLE_MPDAO_VOTER_INFO_EXAMPLE],
}

SIMPLE_POT_FACTORY_EXAMPLE = {
"account": "v1.potfactory.potlock.near",
Expand Down Expand Up @@ -335,11 +364,3 @@ def get_staking_token_balance(self, obj):
def get_staking_token_id(self, obj):
return "meta-pool.near"




class PaginatedMpdaoVotersResponseSerializer(serializers.Serializer):
count = serializers.IntegerField()
next = serializers.CharField(allow_null=True)
previous = serializers.CharField(allow_null=True)
results = MpdaoVoterSerializer(many=True)

0 comments on commit fb90a57

Please sign in to comment.