210 lines
7.0 KiB
Python
210 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Import Raid Guild Members from CSV
|
|
|
|
This script imports Raid Guild DAO members from a CSV file exported from DAOhaus.
|
|
It adds the members to the database with proper DAO membership records and notes.
|
|
|
|
Usage:
|
|
python import_raid_guild_csv.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import csv
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from datetime import datetime
|
|
from dotenv import load_dotenv
|
|
|
|
# Add parent directory to path to import utils
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from utils.db_connector import DatabaseConnector
|
|
from utils.logger import setup_logger
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Setup logging
|
|
logger = setup_logger("raid_guild_importer")
|
|
|
|
class RaidGuildImporter:
|
|
"""Importer for Raid Guild members from CSV file"""
|
|
|
|
def __init__(self, csv_path: str):
|
|
"""Initialize the importer"""
|
|
self.csv_path = csv_path
|
|
|
|
# Initialize database
|
|
self.db = DatabaseConnector()
|
|
|
|
# Register data source
|
|
self.data_source_id = self.register_data_source()
|
|
|
|
def register_data_source(self) -> str:
|
|
"""Register the data source in the database"""
|
|
query = """
|
|
INSERT INTO "DataSource" (
|
|
id, name, type, description, "createdAt", "updatedAt"
|
|
)
|
|
VALUES (
|
|
gen_random_uuid(), %(name)s, %(type)s, %(description)s, NOW(), NOW()
|
|
)
|
|
ON CONFLICT (name) DO UPDATE
|
|
SET type = EXCLUDED.type,
|
|
description = EXCLUDED.description,
|
|
"updatedAt" = NOW()
|
|
RETURNING id
|
|
"""
|
|
|
|
result = self.db.execute_query(query, {
|
|
"name": "Raid Guild DAO CSV",
|
|
"description": "Raid Guild is a Moloch DAO on Gnosis Chain with 151 members. Imported from CSV export.",
|
|
"type": "blockchain"
|
|
})
|
|
|
|
data_source_id = result[0]["id"]
|
|
logger.info(f"Registered data source with ID: {data_source_id}")
|
|
return data_source_id
|
|
|
|
def read_csv(self) -> List[Dict[str, Any]]:
|
|
"""Read the CSV file and return a list of members"""
|
|
members = []
|
|
|
|
try:
|
|
with open(self.csv_path, 'r') as csvfile:
|
|
reader = csv.DictReader(csvfile)
|
|
for row in reader:
|
|
# Only include members that exist and haven't ragequit
|
|
if row.get('exists', '').lower() == 'true' and row.get('didRagequit', '').lower() == 'false':
|
|
members.append({
|
|
"address": row.get('memberAddress', '').lower(),
|
|
"delegateKey": row.get('delegateKey', '').lower(),
|
|
"shares": int(row.get('shares', 0)),
|
|
"loot": int(row.get('loot', 0)),
|
|
"joined_at": row.get('createdAt', None)
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"Error reading CSV file: {e}")
|
|
raise
|
|
|
|
logger.info(f"Read {len(members)} members from CSV file")
|
|
return members
|
|
|
|
def process_member(self, member: Dict[str, Any]) -> Optional[str]:
|
|
"""Process a member and add to the database"""
|
|
address = member["address"]
|
|
|
|
# Check if contact already exists
|
|
query = 'SELECT id FROM "Contact" WHERE "ethereumAddress" = %(address)s'
|
|
result = self.db.execute_query(query, {"address": address})
|
|
|
|
if result:
|
|
contact_id = result[0]["id"]
|
|
logger.info(f"Contact already exists for {address} with ID {contact_id}")
|
|
else:
|
|
# Create new contact
|
|
query = """
|
|
INSERT INTO "Contact" (
|
|
id, "ethereumAddress", name, "createdAt", "updatedAt"
|
|
)
|
|
VALUES (
|
|
gen_random_uuid(), %(address)s, %(name)s, NOW(), NOW()
|
|
)
|
|
RETURNING id
|
|
"""
|
|
|
|
result = self.db.execute_query(query, {
|
|
"address": address,
|
|
"name": f"Raid Guild Member"
|
|
})
|
|
|
|
if not result:
|
|
logger.error(f"Failed to add contact for {address}")
|
|
return None
|
|
|
|
contact_id = result[0]["id"]
|
|
logger.info(f"Added new contact: {address} with ID {contact_id}")
|
|
|
|
# Add DAO membership
|
|
query = """
|
|
INSERT INTO "DaoMembership" (
|
|
id, "contactId", "daoName", "daoType", "joinedAt", "createdAt", "updatedAt"
|
|
)
|
|
VALUES (
|
|
gen_random_uuid(), %(contact_id)s, %(dao_name)s, %(dao_type)s,
|
|
%(joined_at)s, NOW(), NOW()
|
|
)
|
|
ON CONFLICT ("contactId", "daoName") DO UPDATE
|
|
SET "daoType" = EXCLUDED."daoType",
|
|
"joinedAt" = EXCLUDED."joinedAt",
|
|
"updatedAt" = NOW()
|
|
"""
|
|
|
|
joined_at = None
|
|
if member.get("joined_at"):
|
|
try:
|
|
# Convert Unix timestamp to datetime
|
|
joined_at_timestamp = int(member["joined_at"])
|
|
joined_at = datetime.fromtimestamp(joined_at_timestamp)
|
|
except (ValueError, TypeError):
|
|
joined_at = None
|
|
|
|
self.db.execute_update(query, {
|
|
"contact_id": contact_id,
|
|
"dao_name": "Raid Guild",
|
|
"dao_type": "Moloch DAO",
|
|
"joined_at": joined_at
|
|
})
|
|
|
|
# Add a note about the member's shares and loot
|
|
query = """
|
|
INSERT INTO "Note" (
|
|
id, "contactId", content, "createdAt", "updatedAt"
|
|
)
|
|
VALUES (
|
|
gen_random_uuid(), %(contact_id)s, %(content)s, NOW(), NOW()
|
|
)
|
|
"""
|
|
|
|
self.db.execute_update(query, {
|
|
"contact_id": contact_id,
|
|
"content": f"Member of Raid Guild DAO (0xfe1084bc16427e5eb7f13fc19bcd4e641f7d571f) with {member['shares']} shares and {member['loot']} loot"
|
|
})
|
|
|
|
return contact_id
|
|
|
|
def run(self):
|
|
"""Run the importer"""
|
|
logger.info(f"Starting Raid Guild member import from {self.csv_path}")
|
|
|
|
# Read members from CSV
|
|
members = self.read_csv()
|
|
|
|
# Process members
|
|
processed_count = 0
|
|
for member in members:
|
|
if self.process_member(member):
|
|
processed_count += 1
|
|
|
|
logger.info(f"Processed {processed_count} members out of {len(members)} found")
|
|
return processed_count
|
|
|
|
def main():
|
|
"""Main function"""
|
|
try:
|
|
csv_path = os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"raid-guild-hkr_Members_1742163047.csv"
|
|
)
|
|
|
|
importer = RaidGuildImporter(csv_path)
|
|
processed_count = importer.run()
|
|
logger.info(f"Import completed successfully. Processed {processed_count} members.")
|
|
return 0
|
|
except Exception as e:
|
|
logger.exception(f"Error running importer: {e}")
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |