Skip to content

Revised request body #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 62 additions & 41 deletions services/webhooks/dao/handler.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
"""Handler for DAO webhook payloads."""

from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from uuid import UUID

from backend.factory import backend
from backend.models import ContractStatus, DAOCreate, ExtensionCreate, TokenCreate
from lib.logger import configure_logger
from services.webhooks.base import WebhookHandler
from services.webhooks.dao.models import (
ContractResponse,
DAOWebhookPayload,
AIBTCCoreWebhookPayload, # Changed from DAOWebhookPayload
AIBTCCoreRequestContract, # For type hinting if needed, though direct use is in parsed_data
DAOWebhookResponse,
ContractType,
TokenSubtype,
)


Expand All @@ -27,11 +29,11 @@ def __init__(self):
self.logger = configure_logger(self.__class__.__name__)
self.db = backend

async def handle(self, parsed_data: DAOWebhookPayload) -> Dict[str, Any]:
"""Handle the parsed DAO webhook data.
async def handle(self, parsed_data: AIBTCCoreWebhookPayload) -> Dict[str, Any]:
"""Handle the parsed AIBTCCoreWebhookPayload data.

Args:
parsed_data: The parsed and validated DAO webhook payload
parsed_data: The parsed and validated AIBTCCoreWebhookPayload

Returns:
Dict containing the result of handling the webhook with created entities
Expand All @@ -40,62 +42,80 @@ async def handle(self, parsed_data: DAOWebhookPayload) -> Dict[str, Any]:
Exception: If there is an error creating any of the entities
"""
try:
self.logger.info(f"Handling DAO webhook for '{parsed_data.name}'")
self.logger.info(
f"Handling DAO webhook (new structure) for '{parsed_data.name}'"
)

# Create the DAO
dao_create = DAOCreate(
name=parsed_data.name,
mission=parsed_data.mission,
description=parsed_data.description,
description=parsed_data.mission, # Use mission for description
is_deployed=True,
is_broadcasted=True,
)

dao = self.db.create_dao(dao_create)
self.logger.info(f"Created DAO with ID: {dao.id}")

# Create extensions
extension_ids: List[UUID] = []
for ext_data in parsed_data.extensions:
# All extensions in this payload are contract definitions, not deployed contracts
# Set status as DRAFT since they're not deployed yet
contract_principal = None
tx_id = None
status = ContractStatus.DEPLOYED

token_contract_entry: Optional[AIBTCCoreRequestContract] = None

for contract_item in parsed_data.contracts:
# Identify the main token contract from the list
# This condition might need to be more specific based on your contract naming or type/subtype conventions
if (
contract_item.type == ContractType.TOKEN
and contract_item.subtype == TokenSubtype.DAO
):
if token_contract_entry is not None:
# Handle case where multiple token contracts are unexpectedly found
self.logger.warning(
f"Multiple token contracts found for DAO '{parsed_data.name}'. Using the first one found."
)
# Or raise an error: raise ValueError("Multiple token contracts found")
token_contract_entry = contract_item
continue # Don't process the token as a generic extension here

# Create extensions for other contracts
# The 'deployer' (contract_item.deployer) is available here but not passed to ExtensionCreate
extension_create = ExtensionCreate(
dao_id=dao.id,
type=ext_data.type,
subtype=ext_data.subtype,
contract_principal=contract_principal,
tx_id=tx_id,
status=status,
type=contract_item.type,
subtype=contract_item.subtype,
contract_principal=contract_item.contract_principal,
tx_id=contract_item.tx_id,
status=ContractStatus.DEPLOYED, # Assuming DEPLOYED as tx_id is present
)

extension = self.db.create_extension(extension_create)
extension_ids.append(extension.id)
self.logger.info(
f"Created extension with ID: {extension.id} for type: {ext_data.type} and subtype: {ext_data.subtype}"
f"Created extension with ID: {extension.id} for type: {contract_item.type} and subtype: {contract_item.subtype}"
)

if token_contract_entry is None:
self.logger.error(
f"Token contract entry not found in contracts list for DAO '{parsed_data.name}'"
)
raise ValueError("Token contract entry not found in contracts list")

# Create token
# The 'deployer' (token_contract_entry.deployer) is available here but not passed to TokenCreate
token_create = TokenCreate(
dao_id=dao.id,
contract_principal=parsed_data.token.contract_principal,
tx_id=parsed_data.token.tx_id,
name=parsed_data.token.name,
description=parsed_data.token.description,
symbol=parsed_data.token.symbol,
decimals=parsed_data.token.decimals,
max_supply=parsed_data.token.max_supply,
uri=parsed_data.token.uri,
image_url=parsed_data.token.image_url,
x_url=parsed_data.token.x_url,
telegram_url=parsed_data.token.telegram_url,
website_url=parsed_data.token.website_url,
status=ContractStatus.DEPLOYED,
contract_principal=token_contract_entry.contract_principal,
tx_id=token_contract_entry.tx_id,
name=parsed_data.token_info.symbol, # Use symbol from token_info as name
description=parsed_data.mission, # Use mission for description
symbol=parsed_data.token_info.symbol,
decimals=parsed_data.token_info.decimals,
max_supply=parsed_data.token_info.max_supply,
uri=parsed_data.token_info.uri,
image_url=parsed_data.token_info.image_url,
x_url=parsed_data.token_info.x_url,
telegram_url=parsed_data.token_info.telegram_url,
website_url=parsed_data.token_info.website_url,
status=ContractStatus.DEPLOYED, # Assuming DEPLOYED
)

token = self.db.create_token(token_create)
self.logger.info(f"Created token with ID: {token.id}")

Expand All @@ -105,13 +125,14 @@ async def handle(self, parsed_data: DAOWebhookPayload) -> Dict[str, Any]:
extension_ids=extension_ids if extension_ids else None,
token_id=token.id,
)

return {
"success": True,
"message": f"Successfully created DAO '{dao.name}' with ID: {dao.id}",
"message": f"Successfully created DAO '{dao.name}' with ID: {dao.id} using new structure",
"data": response.model_dump(),
}

except Exception as e:
self.logger.error(f"Error handling DAO webhook: {str(e)}", exc_info=True)
self.logger.error(
f"Error handling DAO webhook (new structure): {str(e)}", exc_info=True
)
raise
30 changes: 30 additions & 0 deletions services/webhooks/dao/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,33 @@ class DAOWebhookResponse(BaseModel):
dao_id: UUID
extension_ids: Optional[List[UUID]] = None
token_id: Optional[UUID] = None


class AIBTCCoreRequestContract(BaseModel): # New model
name: str
display_name: str
type: ContractType
subtype: str # Keeping as string for flexibility as per original ContractResponse
tx_id: str
deployer: str # Will be parsed from payload but not stored in DB
contract_principal: str

model_config = ConfigDict(populate_by_name=True)


class AIBTCCoreRequestTokenInfo(BaseModel): # New model
symbol: str
decimals: int
max_supply: str
uri: str
image_url: str
x_url: Optional[str] = None
telegram_url: Optional[str] = None
website_url: Optional[str] = None


class AIBTCCoreWebhookPayload(BaseModel): # New model for the entire payload
name: str
mission: str
contracts: List[AIBTCCoreRequestContract]
token_info: AIBTCCoreRequestTokenInfo
22 changes: 11 additions & 11 deletions services/webhooks/dao/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from lib.logger import configure_logger
from services.webhooks.base import WebhookParser
from services.webhooks.dao.models import DAOWebhookPayload
from services.webhooks.dao.models import AIBTCCoreWebhookPayload


class DAOParser(WebhookParser):
Expand All @@ -19,29 +19,29 @@ def __init__(self):
super().__init__()
self.logger = configure_logger(self.__class__.__name__)

def parse(self, raw_data: Dict[str, Any]) -> DAOWebhookPayload:
"""Parse the raw webhook data into a structured DAO payload.
def parse(self, raw_data: Dict[str, Any]) -> AIBTCCoreWebhookPayload:
"""Parse the raw webhook data into a structured AIBTCCoreWebhookPayload payload.

Args:
raw_data: The raw webhook payload containing DAO, extensions, and token data
raw_data: The raw webhook payload containing DAO, contracts, and token_info data

Returns:
DAOWebhookPayload: A structured representation of the DAO creation data
AIBTCCoreWebhookPayload: A structured representation of the DAO creation data

Raises:
ValueError: If the payload is missing required fields or has invalid data
"""
try:
self.logger.info("Parsing DAO webhook payload")
self.logger.info("Parsing DAO webhook payload using AIBTCCoreWebhookPayload structure")

# Validate the payload using Pydantic
dao_payload = DAOWebhookPayload(**raw_data)
# Validate the payload using the new Pydantic model
dao_payload = AIBTCCoreWebhookPayload(**raw_data)

self.logger.info(
f"Successfully parsed DAO webhook payload for '{dao_payload.name}'"
f"Successfully parsed DAO webhook payload for '{dao_payload.name}' using new structure"
)
return dao_payload

except Exception as e:
self.logger.error(f"Error parsing DAO webhook payload: {str(e)}")
raise ValueError(f"Invalid DAO webhook payload: {str(e)}")
self.logger.error(f"Error parsing DAO webhook payload with new structure: {str(e)}")
raise ValueError(f"Invalid DAO webhook payload (new structure): {str(e)}")