Merge pull request #113 from Hestia-Homes/feature/tasks

added line items'
This commit is contained in:
Jun-te Kim 2025-11-28 19:27:54 +00:00 committed by GitHub
commit 3ce73081d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 66 additions and 178 deletions

View file

@ -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")

View file

@ -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