diff --git a/indexer_app/handler.py b/indexer_app/handler.py index a3858fe..5c251cf 100644 --- a/indexer_app/handler.py +++ b/indexer_app/handler.py @@ -21,6 +21,7 @@ handle_new_pot, handle_new_pot_factory, handle_payout_challenge, + handle_payout_challenge_response, handle_pot_application, handle_pot_application_status_change, handle_set_payouts, @@ -270,7 +271,14 @@ async def handle_streamer_message(streamer_message: near_primitives.StreamerMess case "challenge_payouts": logger.info(f"challenge payout: {args_dict}") await handle_payout_challenge( - args_dict, receiver_id, signer_id, receipt.receipt_id + args_dict, receiver_id, signer_id, receipt.receipt_id, created_at + ) + break + + case "admin_update_payouts_challenge": + logger.info(f"challenge payout: {args_dict}") + await handle_payout_challenge_response( + args_dict, receiver_id, signer_id, receipt.receipt_id, created_at ) break diff --git a/indexer_app/utils.py b/indexer_app/utils.py index dc4b986..e3fd803 100644 --- a/indexer_app/utils.py +++ b/indexer_app/utils.py @@ -1,7 +1,7 @@ import base64 import decimal import json -from datetime import date, datetime +from datetime import datetime import requests from django.conf import settings @@ -11,7 +11,6 @@ from accounts.models import Account from activities.models import Activity -from base.utils import format_date, format_to_near from donations.models import Donation from indexer_app.models import BlockHeight from lists.models import List, ListRegistration, ListUpvote @@ -22,8 +21,8 @@ PotFactory, PotPayout, PotPayoutChallenge, + PotPayoutChallengeAdminResponse, ) -from tokens.models import Token, TokenHistoricalPrice from .logging import logger @@ -38,152 +37,177 @@ async def handle_new_pot( receiptId: str, created_at: datetime, ): - logger.info("new pot deployment process... upsert accounts,") - - # Upsert accounts - owner, _ = await Account.objects.aget_or_create(id=data["owner"]) - signer, _ = await Account.objects.aget_or_create(id=signerId) - receiver, _ = await Account.objects.aget_or_create(id=receiverId) - - logger.info("upsert chef") - if data.get("chef"): - chef, _ = await Account.objects.aget_or_create(id=data["chef"]) - - # Create Pot object - logger.info("create pot....") - potObject = await Pot.objects.acreate( - id=receiver, - pot_factory_id=predecessorId, - deployer=signer, - deployed_at=created_at, - source_metadata=data["source_metadata"], - owner_id=data["owner"], - chef_id=data.get("chef"), - name=data["pot_name"], - description=data["pot_description"], - max_approved_applicants=data["max_projects"], - base_currency="near", - application_start=datetime.fromtimestamp(data["application_start_ms"] / 1000), - application_end=datetime.fromtimestamp(data["application_end_ms"] / 1000), - matching_round_start=datetime.fromtimestamp( - data["public_round_start_ms"] / 1000 - ), - matching_round_end=datetime.fromtimestamp(data["public_round_end_ms"] / 1000), - registry_provider=data["registry_provider"], - min_matching_pool_donation_amount=data["min_matching_pool_donation_amount"], - sybil_wrapper_provider=data["sybil_wrapper_provider"], - custom_sybil_checks=data.get("custom_sybil_checks"), - custom_min_threshold_score=data.get("custom_min_threshold_score"), - referral_fee_matching_pool_basis_points=data[ - "referral_fee_matching_pool_basis_points" - ], - referral_fee_public_round_basis_points=data[ - "referral_fee_public_round_basis_points" - ], - chef_fee_basis_points=data["chef_fee_basis_points"], - total_matching_pool="0", - matching_pool_balance="0", - matching_pool_donations_count=0, - total_public_donations="0", - public_donations_count=0, - cooldown_period_ms=None, - all_paid_out=False, - protocol_config_provider=data["protocol_config_provider"], - ) + try: - # Add admins to the Pot - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create(id=admin_id) - potObject.admins.aadd(admin) - - # Create activity object - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Deploy_Pot", - action_result=data, - tx_hash=receiptId, - ) + logger.info("new pot deployment process... upsert accounts,") + + # Upsert accounts + owner, _ = await Account.objects.aget_or_create(id=data["owner"]) + signer, _ = await Account.objects.aget_or_create(id=signerId) + receiver, _ = await Account.objects.aget_or_create(id=receiverId) + + logger.info("upsert chef") + if data.get("chef"): + chef, _ = await Account.objects.aget_or_create(id=data["chef"]) + + # Create Pot object + logger.info("create pot....") + pot_defaults = { + "pot_factory_id": predecessorId, + "deployer": signer, + "deployed_at": created_at, + "source_metadata": data["source_metadata"], + "owner_id": data["owner"], + "chef_id": data.get("chef"), + "name": data["pot_name"], + "description": data["pot_description"], + "max_approved_applicants": data["max_projects"], + "base_currency": "near", + "application_start": datetime.fromtimestamp( + data["application_start_ms"] / 1000 + ), + "application_end": datetime.fromtimestamp( + data["application_end_ms"] / 1000 + ), + "matching_round_start": datetime.fromtimestamp( + data["public_round_start_ms"] / 1000 + ), + "matching_round_end": datetime.fromtimestamp( + data["public_round_end_ms"] / 1000 + ), + "registry_provider": data["registry_provider"], + "min_matching_pool_donation_amount": data[ + "min_matching_pool_donation_amount" + ], + "sybil_wrapper_provider": data["sybil_wrapper_provider"], + "custom_sybil_checks": data.get("custom_sybil_checks"), + "custom_min_threshold_score": data.get("custom_min_threshold_score"), + "referral_fee_matching_pool_basis_points": data[ + "referral_fee_matching_pool_basis_points" + ], + "referral_fee_public_round_basis_points": data[ + "referral_fee_public_round_basis_points" + ], + "chef_fee_basis_points": data["chef_fee_basis_points"], + "total_matching_pool": "0", + "matching_pool_balance": "0", + "matching_pool_donations_count": 0, + "total_public_donations": "0", + "public_donations_count": 0, + "cooldown_period_ms": None, + "all_paid_out": False, + "protocol_config_provider": data["protocol_config_provider"], + } + potObject = await Pot.objects.aupdate_or_create( + id=receiver, defaults=pot_defaults + ) + + # Add admins to the Pot + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create(id=admin_id) + potObject.admins.aadd(admin) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=data, type="Deploy_Pot", defaults=defaults + ) + except Exception as e: + logger.error(f"Failed to handle new pot, Error: {e}") async def handle_new_pot_factory(data: dict, receiverId: str, created_at: datetime): - logger.info("upserting accounts...") + try: - # Upsert accounts - owner, _ = await Account.objects.aget_or_create( - id=data["owner"], - ) - protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( - id=data["protocol_fee_recipient_account"], - ) + logger.info("upserting accounts...") - receiver, _ = await Account.objects.aget_or_create( - id=receiverId, - ) + # Upsert accounts + owner, _ = await Account.objects.aget_or_create( + id=data["owner"], + ) + protocol_fee_recipient_account, _ = await Account.objects.aget_or_create( + id=data["protocol_fee_recipient_account"], + ) - logger.info("creating factory....") - # Create Factory object - factory = await PotFactory.objects.acreate( - id=receiver, - owner=owner, - deployed_at=created_at, - source_metadata=data["source_metadata"], - protocol_fee_basis_points=data["protocol_fee_basis_points"], - protocol_fee_recipient=protocol_fee_recipient_account, - require_whitelist=data["require_whitelist"], - ) + receiver, _ = await Account.objects.aget_or_create( + id=receiverId, + ) - # Add admins to the PotFactory - if data.get("admins"): - for admin_id in data["admins"]: - admin, _ = await Account.objects.aget_or_create( - id=admin_id, - ) - await factory.admins.aadd(admin) + logger.info("creating factory....") + defaults = { + "owner": owner, + "deployed_at": created_at, + "source_metadata": data["source_metadata"], + "protocol_fee_basis_points": data["protocol_fee_basis_points"], + "protocol_fee_recipient": protocol_fee_recipient_account, + "require_whitelist": data["require_whitelist"], + } + # Create Factory object + factory, factory_created = await PotFactory.objects.aupdate_or_create( + id=receiver, defaults=defaults + ) - # Add whitelisted deployers to the PotFactory - if data.get("whitelisted_deployers"): - for deployer_id in data["whitelisted_deployers"]: - deployer, _ = await Account.objects.aget_or_create(id=deployer_id) - await factory.whitelisted_deployers.aadd(deployer) + # Add admins to the PotFactory + if data.get("admins"): + for admin_id in data["admins"]: + admin, _ = await Account.objects.aget_or_create( + id=admin_id, + ) + await factory.admins.aadd(admin) + + # Add whitelisted deployers to the PotFactory + if data.get("whitelisted_deployers"): + for deployer_id in data["whitelisted_deployers"]: + deployer, _ = await Account.objects.aget_or_create(id=deployer_id) + await factory.whitelisted_deployers.aadd(deployer) + except Exception as e: + logger.error(f"Failed to handle new pot Factory, Error: {e}") async def handle_new_list(signerId: str, receiverId: str, status_obj: ExecutionOutcome): # receipt = block.receipts().filter(receiptId=receiptId)[0] - data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") - ) + try: - logger.info(f"creating list..... {data}") - - listObject = await List.objects.acreate( - id=data["id"], - owner_id=data["owner"], - default_registration_status=data["default_registration_status"], - name=data["name"], - description=data["description"], - cover_image_url=data["cover_image_url"], - admin_only_registrations=data["admin_only_registrations"], - created_at=datetime.fromtimestamp(data["created_at"] / 1000), - updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), - ) + data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + ) - logger.info("upserting involveed accts...") + logger.info(f"creating list..... {data}") + + listObject = await List.objects.acreate( + id=data["id"], + owner_id=data["owner"], + default_registration_status=data["default_registration_status"], + name=data["name"], + description=data["description"], + cover_image_url=data["cover_image_url"], + admin_only_registrations=data["admin_only_registrations"], + created_at=datetime.fromtimestamp(data["created_at"] / 1000), + updated_at=datetime.fromtimestamp(data["updated_at"] / 1000), + ) - await Account.objects.aget_or_create(id=data["owner"]) + logger.info("upserting involveed accts...") - await Account.objects.aget_or_create(id=signerId) + await Account.objects.aget_or_create(id=data["owner"]) - await Account.objects.aget_or_create(id=receiverId) + await Account.objects.aget_or_create(id=signerId) - if data.get("admins"): - for admin_id in data["admins"]: - admin_object, _ = await Account.objects.aget_or_create( - id=admin_id, - ) - await listObject.admins.aadd(admin_object) + await Account.objects.aget_or_create(id=receiverId) + + if data.get("admins"): + for admin_id in data["admins"]: + admin_object, _ = await Account.objects.aget_or_create( + id=admin_id, + ) + await listObject.admins.aadd(admin_object) + except Exception as e: + logger.error(f"Failed to handle new list, Error: {e}") async def handle_new_list_registration( @@ -240,13 +264,15 @@ async def handle_new_list_registration( # Insert activity try: - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=insert_data[0]["submitted_at"], - type="Register_Batch", - action_result=reg_data, - tx_hash=receipt.receipt_id, + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": datetime.fromtimestamp(insert_data[0]["submitted_at"] / 1000), + "tx_hash": receipt.receipt_id, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=reg_data, type="Register_Batch", defaults=defaults ) except Exception as e: logger.error(f"Encountered error trying to insert activity: {e}") @@ -283,47 +309,61 @@ async def handle_pot_application( status_obj: ExecutionOutcome, created_at: datetime, ): - # receipt = block.receipts().filter(lambda receipt: receipt.receiptId == receiptId)[0] - result = status_obj.status.get("SuccessValue") - if not result: - return + try: - appl_data = json.loads(base64.b64decode(result).decode("utf-8")) - logger.info(f"new pot application data: {data}, {appl_data}") + # receipt = block.receipts().filter(lambda receipt: receipt.receiptId == receiptId)[0] + result = status_obj.status.get("SuccessValue") + if not result: + return - # Update or create the account - project, _ = await Account.objects.aget_or_create( - id=data["project_id"], - ) + appl_data = json.loads(base64.b64decode(result).decode("utf-8")) + logger.info(f"new pot application data: {data}, {appl_data}") - signer, _ = await Account.objects.aget_or_create( - id=signerId, - ) + # Update or create the account + project, _ = await Account.objects.aget_or_create( + id=data["project_id"], + ) - # Create the PotApplication object - logger.info("creating application.......") - application = await PotApplication.objects.acreate( - pot_id=receiverId, - applicant=project, - message=appl_data["message"], - submitted_at=datetime.fromtimestamp(appl_data["submitted_at"] / 1000), - updated_at=created_at, - status=appl_data["status"], - tx_hash=receipt.receipt_id, - ) + signer, _ = await Account.objects.aget_or_create( + id=signerId, + ) - # Create the activity object - logger.info("creating activity for action....") - await Activity.objects.acreate( - signer=signer, - receiver_id=receiverId, - timestamp=application.submitted_at, - type="Submit_Application", - action_result=appl_data, - tx_hash=receipt.receipt_id, - ) + # Create the PotApplication object + logger.info("creating application.......") + appl_defaults = { + "message": appl_data["message"], + "submitted_at": datetime.fromtimestamp(appl_data["submitted_at"] / 1000), + "updated_at": created_at, + "status": appl_data["status"], + "tx_hash": receipt.receipt_id, + } + application, application_created = ( + await PotApplication.objects.aupdate_or_create( + applicant=project, + pot_id=receiverId, + defaults=appl_defaults, + ) + ) - logger.info("PotApplication and Activity created successfully.") + # Create the activity object + logger.info("creating activity for action....") + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receipt.receipt_id, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=appl_data, type="Submit_Application", defaults=defaults + ) + + logger.info( + f"PotApplication and Activity created successfully, {activity_created}" + ) + except Exception as e: + logger.error(f"Failed to handle pot application, Error: {e}") async def handle_pot_application_status_change( @@ -333,166 +373,227 @@ async def handle_pot_application_status_change( receipt: Receipt, status_obj: ExecutionOutcome, ): - logger.info(f"pot application update data: {data}, {receiverId}") + try: - # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) - update_data = json.loads( - base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") - ) + logger.info(f"pot application update data: {data}, {receiverId}") - # Retrieve the PotApplication object - appl = await PotApplication.objects.filter( - applicant_id=data["project_id"] - ).afirst() # TODO: handle this being None - - # Create the PotApplicationReview object - logger.info(f"create review...... {appl}") - updated_at = datetime.fromtimestamp(update_data.get("updated_at") / 1000) - await PotApplicationReview.objects.acreate( - application_id=appl.id, - reviewer_id=signerId, - notes=update_data.get("review_notes"), - status=update_data["status"], - reviewed_at=updated_at, - tx_hash=receipt.receipt_id, - ) + # receipt = next(receipt for receipt in block.receipts() if receipt.receiptId == receiptId) + update_data = json.loads( + base64.b64decode(status_obj.status["SuccessValue"]).decode("utf-8") + ) - # Update the PotApplication object - await PotApplication.objects.filter(applicant_id=data["project_id"]).aupdate( - **{"status": update_data["status"], "updated_at": updated_at} - ) + # Retrieve the PotApplication object + appl = await PotApplication.objects.filter( + applicant_id=data["project_id"] + ).afirst() # TODO: handle this being None + + # Create the PotApplicationReview object + logger.info(f"create review...... {appl}") + updated_at = datetime.fromtimestamp(update_data.get("updated_at") / 1000) + + defaults = { + "notes": update_data.get("review_notes"), + "status": update_data["status"], + "tx_hash": receipt.receipt_id, + } + + await PotApplicationReview.objects.aupdate_or_create( + application_id=appl.id, + reviewer_id=signerId, + reviewed_at=updated_at, + defaults=defaults, + ) - logger.info("PotApplicationReview and PotApplication updated successfully.") + # Update the PotApplication object + await PotApplication.objects.filter(applicant_id=data["project_id"]).aupdate( + **{"status": update_data["status"], "updated_at": updated_at} + ) + + logger.info("PotApplicationReview and PotApplication updated successfully.") + except Exception as e: + logger.warning(f"Failed to change pot application status, Error: {e}") async def handle_default_list_status_change( data: dict, receiverId: str, status_obj: ExecutionOutcome ): - logger.info(f"update project data: {data}, {receiverId}") + try: - result_data = json.loads( - base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") - ) + logger.info(f"update project data: {data}, {receiverId}") - list_id = data.get("registration_id") - list_update = { - "name": result_data["name"], - "owner_id": result_data["owner"], - "default_registration_status": result_data["default_registration_status"], - "admin_only_registrations": result_data["admin_only_registrations"], - "updated_at": result_data["updated_at"], - } - if result_data.get("description"): - list_update["description"] = result_data["description"] - if result_data.get("cover_image_url"): - list_update["cover_image_url"] = result_data["cover_image_url"] + result_data = json.loads( + base64.b64decode(status_obj.status.get("SuccessValue")).decode("utf-8") + ) - await List.objects.filter(id=list_id).aupdate(**list_update) + list_id = data.get("registration_id") + list_update = { + "name": result_data["name"], + "owner_id": result_data["owner"], + "default_registration_status": result_data["default_registration_status"], + "admin_only_registrations": result_data["admin_only_registrations"], + "updated_at": result_data["updated_at"], + } + if result_data.get("description"): + list_update["description"] = result_data["description"] + if result_data.get("cover_image_url"): + list_update["cover_image_url"] = result_data["cover_image_url"] - logger.info("List updated successfully.") + await List.objects.filter(id=list_id).aupdate(**list_update) + + logger.info("List updated successfully.") + except Exception as e: + logger.warning(f"Failed to change list status, Error: {e}") async def handle_list_upvote( data: dict, receiverId: str, signerId: str, receiptId: str ): - logger.info(f"upvote list: {data}, {receiverId}") + try: - acct, _ = await Account.objects.aget_or_create( - id=signerId, - ) + logger.info(f"upvote list: {data}, {receiverId}") - created_at = datetime.now() + acct, _ = await Account.objects.aget_or_create( + id=signerId, + ) - await ListUpvote.objects.acreate( - list_id=data.get("list_id") or receiverId, - account_id=signerId, - created_at=created_at, - ) + created_at = datetime.now() - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Upvote", - action_result=data, - tx_hash=receiptId, - ) + await ListUpvote.objects.aupdate_or_create( + list_id=data.get("list_id") or receiverId, + account_id=signerId, + ) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=data, type="Upvote", defaults=defaults + ) - logger.info("Upvote and activity records created successfully.") + logger.info( + f"Upvote and activity records created successfully. {activity_created}" + ) + except Exception as e: + logger.warning(f"Failed to upvote list, Error: {e}") async def handle_set_payouts(data: dict, receiverId: str, receipt: Receipt): - logger.info(f"set payout data: {data}, {receiverId}") - payouts = data.get("payouts", []) - - insertion_data = [] - for payout in payouts: - # General question: should we register projects as accounts? - potPayout = { - "recipient_id": payout.get("project_id"), - "amount": payout.get("amount"), - "ft_id": payout.get("ft_id", "near"), - "tx_hash": receipt.receipt_id, - } - insertion_data.append(potPayout) + try: + + logger.info(f"set payout data: {data}, {receiverId}") + payouts = data.get("payouts", []) + + insertion_data = [] + for payout in payouts: + # General question: should we register projects as accounts? + potPayout = { + "recipient_id": payout.get("project_id"), + "amount": payout.get("amount"), + "ft_id": payout.get("ft_id", "near"), + "tx_hash": receipt.receipt_id, + } + insertion_data.append(potPayout) - await PotPayout.objects.abulk_create(insertion_data) + await PotPayout.objects.abulk_create(insertion_data, ignore_conflicts=True) + except Exception as e: + logger.warning(f"Failed to set payouts, Error: {e}") async def handle_transfer_payout( data: dict, receiverId: str, receiptId: str, created_at: datetime ): - data = data["payout"] - logger.info(f"fulfill payout data: {data}, {receiverId}") - payout = { - "recipient_id": data["project_id"], - "amount": data["amount"], - "paid_at": data.get("paid_at", created_at), - "tx_hash": receiptId, - } - await PotPayout.objects.filter(recipient_id=data["project_id"]).aupdate(**payout) + try: + + data = data["payout"] + logger.info(f"fulfill payout data: {data}, {receiverId}") + payout = { + "recipient_id": data["project_id"], + "amount": data["amount"], + "paid_at": data.get("paid_at", created_at), + "tx_hash": receiptId, + } + await PotPayout.objects.filter(recipient_id=data["project_id"]).aupdate( + **payout + ) + except Exception as e: + logger.warning(f"Failed to create payout data, Error: {e}") async def handle_payout_challenge( - data: dict, receiverId: str, signerId: str, receiptId: str + data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime ): - logger.info(f"challenging payout..: {data}, {receiverId}") - created_at = datetime.now() - payoutChallenge = { - "challenger_id": signerId, - "pot_id": receiverId, - "created_at": created_at, - "message": data["reason"], - "tx_hash": receiptId, - } - await PotPayoutChallenge.objects.acreate(**payoutChallenge) - - await Activity.objects.acreate( - signer_id=signerId, - receiver_id=receiverId, - timestamp=created_at, - type="Challenge_Payout", - action_result=payoutChallenge, - tx_hash=receiptId, - ) + try: + + logger.info(f"challenging payout..: {data}, {receiverId}") + payoutChallenge = { + "created_at": created_at, + "message": data["reason"], + "tx_hash": receiptId, + } + await PotPayoutChallenge.objects.aupdate_or_create( + challenger_id=signerId, pot_id=receiverId, defaults=payoutChallenge + ) + + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": created_at, + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=payoutChallenge, type="Challenge_Payout", defaults=defaults + ) + except Exception as e: + logger.warning(f"Failed to create payoutchallenge, Error: {e}") + + +async def handle_payout_challenge_response( + data: dict, receiverId: str, signerId: str, receiptId: str, created_at: datetime +): + try: + logger.info(f"responding to payout challenge..: {data}, {receiverId}") + response_defaults = { + "admin": signerId, + "message": data.get("notes"), + "resolved": data.get("resolve_challenge"), + "tx_hash": receiptId, + } + await PotPayoutChallengeAdminResponse.objects.aupdate_or_create( + challenger_id=data["challenger_id"], + pot_id=receiverId, + created_at=created_at, + defaults=response_defaults, + ) + except Exception as e: + logger.error(f"Failed to handle admin challeneg response, Error: {e}") async def handle_list_admin_removal(data, receiverId, signerId, receiptId): - logger.info(f"removing admin...: {data}, {receiverId}") - list_obj = await List.objects.aget(id=data["list_id"]) - - for acct in data["admins"]: - list_obj.admins.remove({"admins_id": acct}) # ?? - - activity = { - "signer_id": signerId, - "receiver_id": receiverId, - "timestamp": datetime.now(), - "type": "Remove_List_Admin", - "tx_hash": receiptId, - } + try: + + logger.info(f"removing admin...: {data}, {receiverId}") + list_obj = await List.objects.aget(id=data["list_id"]) + + for acct in data["admins"]: + list_obj.admins.remove({"admins_id": acct}) # maybe check - await Activity.objects.acreate(**activity) + activity = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": datetime.now(), + "tx_hash": receiptId, + } + + activity, activity_created = await Activity.objects.aupdate_or_create( + type="Remove_List_Admin", defaults=activity + ) + except Exception as e: + logger.warning(f"Failed to remove list admin, Error: {e}") # TODO: Need to abstract some actions. @@ -559,28 +660,32 @@ async def handle_new_donations( (donation_data.get("donated_at") or donation_data.get("donated_at_ms")) / 1000 ) - # Upsert donor account - donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) - recipient = None + try: - if donation_data.get("recipient_id"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["recipient_id"] - ) - if donation_data.get("project_id"): - recipient, _ = await Account.objects.aget_or_create( - id=donation_data["project_id"] - ) + # Upsert donor account + donor, _ = await Account.objects.aget_or_create(id=donation_data["donor_id"]) + recipient = None - if donation_data.get("referrer_id"): - referrer, _ = await Account.objects.aget_or_create( - id=donation_data["referrer_id"] - ) + if donation_data.get("recipient_id"): + recipient, _ = await Account.objects.aget_or_create( + id=donation_data["recipient_id"] + ) + if donation_data.get("project_id"): + recipient, _ = await Account.objects.aget_or_create( + id=donation_data["project_id"] + ) - # Upsert token account - token_acct, _ = await Account.objects.aget_or_create( - id=(donation_data.get("ft_id") or "near") - ) + if donation_data.get("referrer_id"): + referrer, _ = await Account.objects.aget_or_create( + id=donation_data["referrer_id"] + ) + + # Upsert token account + token_acct, _ = await Account.objects.aget_or_create( + id=(donation_data.get("ft_id") or "near") + ) + except Exception as e: + logger.warning(f"Failed to create/get an account involved in donation: {e}") # # Upsert token # try: @@ -630,69 +735,78 @@ async def handle_new_donations( # total_amount_usd = None if not unit_price else unit_price * total_near_amount # net_amount_usd = None if not unit_price else unit_price * net_near_amount - total_amount = donation_data["total_amount"] - - logger.info(f"inserting donations... by {actionName}") - default_data = { - "donor": donor, - "total_amount": total_amount, - "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) - "net_amount_usd": None, - "net_amount": net_amount, - "ft": token_acct, - "message": donation_data.get("message"), - "donated_at": donated_at, - "matching_pool": donation_data.get("matching_pool", False), - "recipient": recipient, - "protocol_fee": donation_data["protocol_fee"], - "referrer": referrer if donation_data.get("referrer_id") else None, - "referrer_fee": donation_data.get("referrer_fee"), - "tx_hash": receipt_obj.receipt_id, - } - logger.info(f"default donation data: {default_data}") + try: - if actionName != "direct": - logger.info("selecting pot to make public donation update") - pot = await Pot.objects.aget(id=receiverId) - default_data["pot"] = pot + total_amount = donation_data["total_amount"] + + logger.info(f"inserting donations... by {actionName}") + default_data = { + "donor": donor, + "total_amount": total_amount, + "total_amount_usd": None, # USD amounts will be added later (could be in pre-save hook) + "net_amount_usd": None, + "net_amount": net_amount, + "ft": token_acct, + "message": donation_data.get("message"), + "donated_at": donated_at, + "matching_pool": donation_data.get("matching_pool", False), + "recipient": recipient, + "protocol_fee": donation_data["protocol_fee"], + "referrer": referrer if donation_data.get("referrer_id") else None, + "referrer_fee": donation_data.get("referrer_fee"), + "tx_hash": receipt_obj.receipt_id, + } + logger.info(f"default donation data: {default_data}") - donation, donation_created = await Donation.objects.aupdate_or_create( - on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data - ) - logger.info(f"Created donation? {donation_created}") - - # fetch USD prices - await donation.fetch_usd_prices_async() # might not need to await this? - - # # convert total_amount_usd and net_amount_usd from None - # if total_amount_usd is None: - # total_amount_usd = 0.0 - # if net_amount_usd is None: - # net_amount_usd = 0.0 - - # Insert or update activity record - activity_type = ( - "Donate_Direct" - if actionName == "direct" - else ( - "Donate_Pot_Matching_Pool" - if donation.matching_pool - else "Donate_Pot_Public" + if actionName != "direct": + logger.info("selecting pot to make public donation update") + pot = await Pot.objects.aget(id=receiverId) + default_data["pot"] = pot + + donation, donation_created = await Donation.objects.aupdate_or_create( + on_chain_id=donation_data["id"], defaults={}, create_defaults=default_data ) - ) - defaults = { - "signer_id": signerId, - "receiver_id": receiverId, - "timestamp": donation.donated_at, - "tx_hash": receipt_obj.receipt_id, - } - activity, activity_created = await Activity.objects.aupdate_or_create( - action_result=donation_data, type=activity_type, defaults=defaults - ) - if activity_created: - logger.info(f"Activity created: {activity}") - else: - logger.info(f"Activity updated: {activity}") + logger.info(f"Created donation? {donation_created}") + + # fetch USD prices + await donation.fetch_usd_prices_async() # might not need to await this? + + # # convert total_amount_usd and net_amount_usd from None + # if total_amount_usd is None: + # total_amount_usd = 0.0 + # if net_amount_usd is None: + # net_amount_usd = 0.0 + + logger.info(f"Created donation? {donation_created}") + # Insert or update activity record + activity_type = ( + "Donate_Direct" + if actionName == "direct" + else ( + "Donate_Pot_Matching_Pool" + if donation.matching_pool + else "Donate_Pot_Public" + ) + ) + defaults = { + "signer_id": signerId, + "receiver_id": receiverId, + "timestamp": donation.donated_at, + "tx_hash": receipt_obj.receipt_id, + } + try: + + activity, activity_created = await Activity.objects.aupdate_or_create( + action_result=donation_data, type=activity_type, defaults=defaults + ) + if activity_created: + logger.info(f"Activity created: {activity}") + else: + logger.info(f"Activity updated: {activity}") + except Exception as e: + logger.info(f"Failed to create Activity: {e}") + except Exception as e: + logger.warning(f"Failed to create/update donation: {e}") ### COMMENTING OUT FOR NOW SINCE WE HAVE PERIODIC JOB RUNNING TO UPDATE ACCOUNT STATS (NB: DOESN'T CURRENTLY COVER POT STATS) ### CAN ALWAYS ADD BACK IF DESIRED diff --git a/lists/migrations/0002_alter_listupvote_options_and_more.py b/lists/migrations/0002_alter_listupvote_options_and_more.py new file mode 100644 index 0000000..af8f5c2 --- /dev/null +++ b/lists/migrations/0002_alter_listupvote_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-05-08 15:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ("lists", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="listupvote", + options={"verbose_name_plural": "ListUpvotes"}, + ), + migrations.AlterUniqueTogether( + name="listupvote", + unique_together={("list", "account")}, + ), + ] diff --git a/lists/models.py b/lists/models.py index a3e93df..7732f1e 100644 --- a/lists/models.py +++ b/lists/models.py @@ -101,6 +101,11 @@ class ListUpvote(models.Model): help_text=_("Upvote creation date."), ) + class Meta: + verbose_name_plural = "ListUpvotes" + + unique_together = (("list", "account"),) + class ListRegistration(models.Model): id = models.AutoField( diff --git a/pots/admin.py b/pots/admin.py index 24862fa..d49ee44 100644 --- a/pots/admin.py +++ b/pots/admin.py @@ -1,5 +1,14 @@ from django.contrib import admin -from .models import PotFactory, Pot, PotApplication, PotPayout, PotPayoutChallenge, PotPayoutChallengeAdminResponse + +from .models import ( + Pot, + PotApplication, + PotFactory, + PotPayout, + PotPayoutChallenge, + PotPayoutChallengeAdminResponse, +) + @admin.register(PotFactory) class PotFactoryAdmin(admin.ModelAdmin): @@ -32,6 +41,6 @@ class PotPayoutChallengeAdmin(admin.ModelAdmin): @admin.register(PotPayoutChallengeAdminResponse) class PotPayoutChallengeAdminResponseAdmin(admin.ModelAdmin): - list_display = ('id', 'challenge', 'admin', 'created_at', 'resolved') + list_display = ('id', 'pot', 'admin', 'created_at', 'resolved') search_fields = ('admin__id', 'challenge__id') list_filter = ('created_at', 'resolved') diff --git a/pots/migrations/0002_alter_potapplication_options_and_more.py b/pots/migrations/0002_alter_potapplication_options_and_more.py new file mode 100644 index 0000000..60f1eed --- /dev/null +++ b/pots/migrations/0002_alter_potapplication_options_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.0.4 on 2024-05-09 12:44 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ("pots", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="potapplication", + options={"verbose_name_plural": "PotApplications"}, + ), + migrations.AlterModelOptions( + name="potapplicationreview", + options={"verbose_name_plural": "PotApplicationReviews"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallenge", + options={"verbose_name_plural": "PayoutChallenges"}, + ), + migrations.AlterModelOptions( + name="potpayoutchallengeadminresponse", + options={"verbose_name_plural": "PotPayoutChallengeResponses"}, + ), + migrations.AddField( + model_name="potpayoutchallengeadminresponse", + name="challenger", + field=models.ForeignKey( + help_text="challenger being responded to.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_admin_responses", + to="accounts.account", + ), + ), + migrations.AddField( + model_name="potpayoutchallengeadminresponse", + name="pot", + field=models.ForeignKey( + help_text="Pot being challenged.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="payout_responses", + to="pots.pot", + ), + ), + migrations.AlterUniqueTogether( + name="potpayoutchallengeadminresponse", + unique_together={("challenger", "pot", "created_at")}, + ), + migrations.AlterUniqueTogether( + name="potapplication", + unique_together={("pot", "applicant")}, + ), + migrations.AlterUniqueTogether( + name="potapplicationreview", + unique_together={("application", "reviewer", "reviewed_at")}, + ), + migrations.AlterUniqueTogether( + name="potpayoutchallenge", + unique_together={("challenger", "pot")}, + ), + migrations.RemoveField( + model_name="potpayoutchallengeadminresponse", + name="challenge", + ), + ] diff --git a/pots/models.py b/pots/models.py index 8941bec..b216713 100644 --- a/pots/models.py +++ b/pots/models.py @@ -332,6 +332,11 @@ class PotApplication(models.Model): help_text=_("Transaction hash."), ) + class Meta: + verbose_name_plural = "PotApplications" + + unique_together = (("pot", "applicant"),) + class PotApplicationReview(models.Model): id = models.AutoField( @@ -378,6 +383,11 @@ class PotApplicationReview(models.Model): help_text=_("Transaction hash."), ) + class Meta: + verbose_name_plural = "PotApplicationReviews" + + unique_together = (("application", "reviewer", "reviewed_at"),) + class PotPayout(models.Model): id = models.AutoField( @@ -468,6 +478,11 @@ class PotPayoutChallenge(models.Model): help_text=_("Challenge message."), ) + class Meta: + verbose_name_plural = "PayoutChallenges" + + unique_together = (("challenger", "pot"),) + class PotPayoutChallengeAdminResponse(models.Model): id = models.AutoField( @@ -475,13 +490,22 @@ class PotPayoutChallengeAdminResponse(models.Model): primary_key=True, help_text=_("Admin response id."), ) - challenge = models.ForeignKey( - PotPayoutChallenge, + challenger = models.ForeignKey( + Account, on_delete=models.CASCADE, - related_name="admin_responses", - null=False, - help_text=_("Challenge responded to."), + related_name="payout_admin_responses", + null=True, + help_text=_("challenger being responded to."), ) + + pot = models.ForeignKey( + Pot, + on_delete=models.CASCADE, + related_name="payout_responses", + null=True, + help_text=_("Pot being challenged."), + ) + admin = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -511,3 +535,8 @@ class PotPayoutChallengeAdminResponse(models.Model): null=False, help_text=_("Transaction hash."), ) + + class Meta: + verbose_name_plural = "PotPayoutChallengeResponses" + + unique_together = (("challenger", "pot", "created_at"),)