#!/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())