diff --git a/deployment/lambda/lambda_example/docker/app.py b/deployment/lambda/lambda_example/docker/app.py index 97e3d13..48c22cc 100644 --- a/deployment/lambda/lambda_example/docker/app.py +++ b/deployment/lambda/lambda_example/docker/app.py @@ -1,181 +1,7 @@ -import os -import json -import time -import requests - - -# ============================================================ -# CONFIG -# ============================================================ - -API_BASE = "https://api.dev.hestia.homes/v1/tasks/" # e.g. https://api.myserver.com/tasks -API_TOKEN = "4QPwbB6hEdUloDVtbBJCUTfGBdBgWwpeavWQ7t5Z" - - -# ============================================================ -# Low-level API wrapper -# ============================================================ - -def api(method, path, body=None): - """Generic function to call your backend Task API.""" - url = f"{API_BASE}{path}" - - headers = { - "Authorization": f"Bearer {API_TOKEN}", - "Content-Type": "application/json", - } - - print(f"[API] {method} {url}") - if body: - print(f"[API] Payload: {json.dumps(body)}") - - response = requests.request( - method, - url, - headers=headers, - data=json.dumps(body) if body else None - ) - - print(f"[API] Response: {response.status_code}") - - if response.status_code >= 400: - print(f"[API ERROR] {response.text}") - raise Exception(f"API Error {response.status_code}: {response.text}") - - return response.json() - - -# ============================================================ -# CloudWatch Logs URL generator (with time range) -# ============================================================ - -def build_cloudwatch_url(context, start_ms, end_ms): - region = context.invoked_function_arn.split(":")[3] - log_group = context.log_group_name - log_stream = context.log_stream_name - - log_group_enc = log_group.replace("/", "$252F") - log_stream_enc = log_stream.replace("/", "$252F") - - return ( - f"https://{region}.console.aws.amazon.com/cloudwatch/home" - f"?region={region}#logsV2:log-groups/log-group/{log_group_enc}" - f"/log-events/{log_stream_enc}" - f"$3Fstart$3D{start_ms}$26end$3D{end_ms}" - ) - - -# ============================================================ -# Subtask lifecycle action helpers -# ============================================================ - -def mark_in_progress(subtask_id): - print(f"[SUBTASK] Marking {subtask_id} as IN PROGRESS") - return api("PUT", f"/subtasks/{subtask_id}/status", {"status": "in progress"}) - - -def mark_complete(subtask_id, outputs, cloud_logs): - print(f"[SUBTASK] Marking {subtask_id} as COMPLETE") - return api("POST", f"/subtasks/{subtask_id}/finalize", { - "status": "complete", - "outputs": outputs, - "cloud_logs_url": cloud_logs - }) - - -def mark_failed(subtask_id, error_message, cloud_logs): - print(f"[SUBTASK] Marking {subtask_id} as FAILED") - return api("POST", f"/subtasks/{subtask_id}/finalize", { - "status": "failed", - "outputs": {"error": error_message}, - "cloud_logs_url": cloud_logs - }) - - -# ============================================================ -# Lambda Handler -# ============================================================ +from etl.hubSpotClient.hubspotClient import HubSpotClient def handler(event, context): - """ - Example Lambda that: - - marks subtask in progress - - prints simulated work steps - - marks complete or failed - - attaches CloudWatch Log URL - """ + hubspot = HubSpotClient() - subtask_id = event["subtask_id"] - - print("=====================================") - print(" 🚀 LAMBDA INVOCATION STARTED") - print(f" 🧩 Subtask ID: {subtask_id}") - print("=====================================") - - start_ms = int(time.time() * 1000) - - try: - # ---- Step 1: Mark subtask IN PROGRESS ---- - mark_in_progress(subtask_id) - - print("\n---- Simulating Work ----") - - print("Step 1: Loading resources...") - time.sleep(0.5) - - print("Step 2: Calling an external service...") - time.sleep(0.7) - - print("Step 3: Processing data...") - time.sleep(1.0) - - # UNCOMMENT TO TEST FAILURE - # raise Exception("Simulated failure for testing") - - print("Step 4: Finalizing outputs...") - time.sleep(0.5) - - outputs = { - "result": "success", - "calculated_value": 123, - "details": "This is example output from the handler.", - "input_params": event.get("params", {}) - } - - print("Work completed successfully.") - - end_ms = int(time.time() * 1000) - cloud_logs_url = build_cloudwatch_url(context, start_ms, end_ms) - - print(f"Generated CloudWatch URL:\n{cloud_logs_url}\n") - - # ---- Step 2: Mark COMPLETE ---- - mark_complete(subtask_id, outputs, cloud_logs_url) - - print("=====================================") - print(" ✅ LAMBDA EXECUTION COMPLETE") - print("=====================================") - - return { - "statusCode": 200, - "body": outputs, - "cloudwatch_logs": cloud_logs_url - } - - except Exception as e: - print("=====================================") - print(" ❌ ERROR IN LAMBDA EXECUTION") - print("=====================================") - print(f"Error: {e}") - - end_ms = int(time.time() * 1000) - cloud_logs_url = build_cloudwatch_url(context, start_ms, end_ms) - - # ---- Step 3: Mark FAILED ---- - mark_failed(subtask_id, str(e), cloud_logs_url) - - return { - "statusCode": 500, - "error": str(e), - "cloudwatch_logs": cloud_logs_url - } + hubspot.add_product_line_item_to_deal("268596250843", "281058046195") + \ No newline at end of file diff --git a/etl/hubSpotClient/hubspotClient.py b/etl/hubSpotClient/hubspotClient.py index adbec50..bb791e9 100644 --- a/etl/hubSpotClient/hubspotClient.py +++ b/etl/hubSpotClient/hubspotClient.py @@ -6,6 +6,10 @@ from hubspot.crm.associations import ApiException import os import requests +from hubspot.crm.objects import SimplePublicObjectInput +from hubspot.crm.associations.v4 import AssociationSpec +from hubspot.crm.associations import ApiException + class Companies(Enum): ABRI = "237615001799" SOUTHERN_HOUSING_GROUP = "109343619305" @@ -261,3 +265,61 @@ class HubSpotClient(): except requests.exceptions.RequestException as e: self.logger.error(f"Failed to download file from HubSpot: {e}") raise + + + def create_line_item_from_product(self, product_id: str, quantity: int = 1): + # Fetch product mapping + product = self.client.crm.products.basic_api.get_by_id( + product_id, + properties=["name", "price", "hs_price"] + ) + + name = product.properties.get("name") + price = ( + product.properties.get("price") + or product.properties.get("hs_price") + or "0" + ) + + # Build line item payload + line_item_input = SimplePublicObjectInput( + properties={ + "hs_product_id": product_id, + "name": name, + "quantity": str(quantity), + "price": price, + "amount": str(float(price) * quantity), + "invoiced": "Outstanding" + } + ) + + # Create line item + line_item = self.client.crm.line_items.basic_api.create(line_item_input) + return line_item.id + + def associate_line_item_to_deal(self, line_item_id: str, deal_id: str): + self.logger.info(f"Associating line item {line_item_id} → deal {deal_id}") + + association_api = self.client.crm.associations.v4.basic_api + + association_api.create( + "0-3", # to object type + deal_id, # to object id + "line_items", # from object type + line_item_id, # from object id + [ + AssociationSpec( + association_category="HUBSPOT_DEFINED", + association_type_id=19 # line_item → deal + ) + ] + ) + + def add_product_line_item_to_deal(self, deal_id: str, product_id: str, quantity: int = 1): + # Step 1: Create the line item from product mapping + line_item_id = self.create_line_item_from_product(product_id, quantity) + + # Step 2: Associate the created line item to the deal + self.associate_line_item_to_deal(line_item_id, deal_id) + + return line_item_id