"""Lambda a360_profile_creator function module.""" import boto3 import config import csv import io import json import re from datetime import datetime, timezone from typing import Any import uuid from src.constants import ( CYPHER_QUERY, PARENT_COMPANIES, ) from config import connector_neo4j def parse_s3_event(event): """Extract bucket and key from S3 event.""" try: detail = event["detail"] bucket_name = detail["bucket"]["name"] object_key = detail["object"]["key"] return bucket_name, object_key except (KeyError, TypeError) as e: raise ValueError(f"Invalid EventBridge S3 event structure: {str(e)}") def to_snake_case(s): return re.sub(r"[\s\-]+", "_", re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s)).lower() def read_csv_from_s3(bucket_name: str, object_key: str) -> list[dict]: """Download and parse CSV file from S3.""" s3_client = boto3.client("s3") try: config.logger.info(f"Reading CSV from s3://{bucket_name}/{object_key}") # Download the file response = s3_client.get_object(Bucket=bucket_name, Key=object_key) csv_content = response["Body"].read().decode("utf-8") # Parse CSV csv_reader = csv.DictReader(io.StringIO(csv_content)) users = [{to_snake_case(k): v for k, v in row.items()} for row in csv_reader] config.logger.info(f"Successfully read {len(users)} users from CSV") return users except Exception as e: config.logger.error(f"Failed to read CSV from S3: {str(e)}") raise e def insert_a360_profile(user: dict, session: Any) -> uuid.UUID | None: """ Insert a user into the A360 profile. :param user: User dictionary containing user details. :return: None or UUID """ try: result = session.run(CYPHER_QUERY, email=user["email"]) record = result.single() if record and record.get("identityId"): return uuid.UUID(record["identityId"]) except ValueError: print( f"Invalid UUID for identityId: {record['identityId']} for user {user['email']}" ) return None return None def generate_pdp_csv_to_s3( uuids: list[uuid.UUID], bucket_name: str, timestamp: str ) -> tuple[str, str]: """Generate a CSV file for PDP backfill and upload it to S3.""" csv_buffer = io.StringIO() fieldnames = ["identity_uuid", "tenant_uuid", "tenant_type", "role", "operation"] writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames) writer.writeheader() for identity_uuid in uuids: for parent_company in PARENT_COMPANIES: writer.writerow( { "identity_uuid": str(identity_uuid), "tenant_uuid": parent_company["uuid"], "tenant_type": "parent_company", "role": "contract_viewer", "operation": "attach", } ) s3_client = boto3.client("s3") csv_content = csv_buffer.getvalue() key = f"{config.A360_OUTPUT_FOLDER}{timestamp}/{timestamp}.csv" try: s3_client.put_object( Bucket=bucket_name, Key=key, Body=csv_content, ContentType="text/csv" ) config.logger.info(f"CSV file uploaded to s3://{bucket_name}/{key}") return f"s3://{bucket_name}/{key}", timestamp except Exception as e: config.logger.error(f"Error uploading CSV to S3: {str(e)}") raise e def generate_manifest_json_to_s3(timestamp: str, bucket_name: str) -> str: """Generate and upload manifest.json file to S3.""" # Create manifest content manifest = { "bucket": f"{config.ENVIRONMENT}-pdp-backfill", "jobs": [ { "job_type": "attach_and_detach", "keys": ["a360-backfill/" + timestamp + ".csv"], } ], } s3_client = boto3.client("s3") manifest_content = json.dumps(manifest, indent=2) # Generate manifest file name with timestamp manifest_key = f"{config.A360_OUTPUT_FOLDER}{timestamp}/manifest.json" try: s3_client.put_object( Bucket=bucket_name, Key=manifest_key, Body=manifest_content, ContentType="application/json", ) config.logger.info( f"Manifest file uploaded to s3://{bucket_name}/{manifest_key}" ) return f"s3://{bucket_name}/{manifest_key}" except Exception as e: config.logger.error(f"Error uploading manifest to S3: {str(e)}") raise e @connector_neo4j.Neo4jSession(transaction=True, use_v2=True, database="graph.db") def handler(event: Any, context: Any) -> dict[str, str]: """Lambda entry point.""" try: session = connector_neo4j.get_session() bucket, key = parse_s3_event(event) users = read_csv_from_s3(bucket, key) user_uuids = [] for user in users: uuid = insert_a360_profile(user, session) if uuid: user_uuids.append(uuid) timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S") s3_url, timestamp = generate_pdp_csv_to_s3( user_uuids, config.ALTAFONTE_USER_CREATION_S3_BUCKET, timestamp ) manifest_json_file = generate_manifest_json_to_s3( timestamp, config.ALTAFONTE_USER_CREATION_S3_BUCKET ) return {"s3_url": s3_url, "manifest_json_file": manifest_json_file} except Exception as e: config.logger.exception(str(e)) raise e