diff --git a/cloud/APIs/twilio.py b/cloud/APIs/twilio.py new file mode 100644 index 0000000..e69de29 diff --git a/cloud/APIs/webex.py b/cloud/APIs/webex.py index 93bda44..5d80197 100644 --- a/cloud/APIs/webex.py +++ b/cloud/APIs/webex.py @@ -66,7 +66,7 @@ class WebexManager: new_doc = self.save_tokens(res.json()) return new_doc["access_token"] - def create_support_room(self, client_name: str, client_email: str) -> str: + def create_support_room(self, client_name: str, client_email: str, alert_type: str, alert_message: str, alert_id: str) -> str: """Creates a team support room and invites the client and yourself.""" access_token = self.get_access_token() headers = { @@ -79,12 +79,13 @@ class WebexManager: f"{self.base_url}/rooms", headers=headers, json={ - "title": f"Smartwave Support: {client_name}", + "title": f"Smartwave Support: {client_name} - {alert_type} ({alert_id})", "teamId": self.team_id } ) room_res.raise_for_status() - room_id = room_res.json()["id"] + room_details = room_res.json() + room_id = room_details["id"] # 2. Add Client via email (Webex sends invite if user does not exist) if client_email: @@ -103,13 +104,39 @@ class WebexManager: ) # 4. Optional: Send initial welcome message in the room + self.send_message( + room_id, + message=f"Support room initialized for {client_name}. An agent will be with you shortly.", + access_token=access_token + ) + self.send_alert_info_message(room_id, alert_type, alert_message, alert_id, access_token=access_token) + + return room_details + + def send_message(self, room_id: str, message: str | None = None, markdown: str | None = None, access_token: str | None = None): + # Fetch the token so it auto-refreshes if needed + if not access_token: + access_token = self.get_access_token() + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json" + } + requests.post( f"{self.base_url}/messages", headers=headers, json={ "roomId": room_id, - "text": f"Support room initialized for {client_name}. An agent will be with you shortly." + "text": message, + "markdown": markdown } - ) + ).raise_for_status() + + def send_alert_info_message(self, room_id: str, alert_type: str, alert_message: str, alert_id: str, added_alert_info: bool = False, access_token: str | None = None): + """Sends a structured alert info message to the specified Webex room.""" + if added_alert_info: + message = f"Additional Alert Details:\n- Type: {alert_type}\n- Message: {alert_message}\n- Alert ID: {alert_id}" + else: + message = f"Alert Details:\n- Type: {alert_type}\n- Message: {alert_message}\n- Alert ID: {alert_id}" - return room_id \ No newline at end of file + self.send_message(room_id, message=message, access_token=access_token) \ No newline at end of file diff --git a/cloud/app.py b/cloud/app.py index 8257f5c..b878448 100644 --- a/cloud/app.py +++ b/cloud/app.py @@ -2,19 +2,17 @@ import os import asyncio import base64 import uuid -import datetime +from datetime import datetime, timedelta, timezone import sys import json -import time -import urllib.parse -import requests -from flask import Flask, request, jsonify, current_app, redirect, url_for +from flask import Flask, request, jsonify, current_app from pymongo import MongoClient from APIs import generate, EdamamAPI from APIs.mqtt import send_command from APIs.webex import WebexManager from microwaveCookPlanner import MicrowaveCookPlanner import safety_checker +import pymongo sys.path.insert(0, '..') try: @@ -177,27 +175,100 @@ async def cooking_params(): @app.route("/alert", methods=["POST"]) def alert(): data = request.get_json() or {} - alert_collection.insert_one(data) - # Extract client metadata from alert payload (or use fallback values) - client_name = data.get("client_name", "Unknown Client") - client_email = data.get("client_email") + # 1. Inject a UTC timestamp so we can easily query the 3-minute window + data["created_at"] = datetime.now(timezone.utc) + saved_alert = alert_collection.insert_one(data) + + # Extract orchestrator_id from the incoming alert + orchestrator_id = data.get("orchestrator_id") + + # Safely extract alert info (prevents errors if "alert" is missing) + alert_info = data.get("alert", {}) + alert_type = alert_info.get("type", "Unknown") + alert_message = alert_info.get("message", "No message provided") + + # --- GET CLIENT METADATA --- + client_name = f"Unknown Client (Orchestrator {orchestrator_id})" if orchestrator_id else "Unknown Client" + client_email = None - room_id = None + if orchestrator_id: + client_doc = client_collection.find_one({ + "devices": { + "$elemMatch": { + "device_type": "orchestrator", + "device_id": str(orchestrator_id) + } + } + }) + + if client_doc: + client_name = client_doc.get("client_name", client_name) + client_email = client_doc.get("contact_info", {}).get("email") + + # --- WEBEX ROOM LOGIC --- + webex_room_id = None webex_status = "skipped" - # Automatically create Webex room if email or client name is supplied - try: - room_id = webex_manager.create_support_room(client_name, client_email) - webex_status = "created" - except Exception as e: - current_app.logger.exception("Failed to create Webex support room") - webex_status = f"failed: {str(e)}" + # 2. Check if a room was created for this orchestrator in the last 3 minutes + three_minutes_ago = datetime.now(timezone.utc) - timedelta(minutes=3) + + recent_alert = alert_collection.find_one({ + "orchestrator_id": orchestrator_id, + "webex_room_id": {"$exists": True, "$ne": None}, + "created_at": {"$gte": three_minutes_ago}, + "_id": {"$ne": saved_alert.inserted_id} # Exclude the alert we just inserted + }, sort=[("created_at", pymongo.DESCENDING)]) + + if recent_alert: + # A room was created recently -> Reuse it and just send a message + webex_room_id = recent_alert["webex_room_id"] + try: + webex_manager.send_alert_info_message( + room_id=webex_room_id, + alert_type=alert_type, + alert_message=alert_message, + alert_id=str(saved_alert.inserted_id) + ) + webex_status = "reused_room" + except Exception as e: + current_app.logger.exception("Failed to send follow-up message to existing Webex room") + webex_status = f"message_failed: {str(e)}" + + else: + # No recent room -> Create a new one + try: + # Assuming your modified create_support_room returns a dict with {"id": ...} + room_details = webex_manager.create_support_room( + client_name, + client_email, + alert_type, + alert_message, + str(saved_alert.inserted_id) # Cast ObjectId to string for JSON serialization + ) + + # Extract ID based on whether your function returns a dict or just the ID string + webex_room_id = room_details["id"] if isinstance(room_details, dict) else room_details + webex_status = "created_new_room" + + except Exception as e: + current_app.logger.exception("Failed to create Webex support room") + webex_status = f"creation_failed: {str(e)}" + + # 3. Update the alert document with the Webex Room ID (whether new or reused) + if webex_room_id: + alert_collection.update_one( + {"_id": saved_alert.inserted_id}, + {"$set": {"webex_room_id": webex_room_id}} + ) + + # --- SEND SMS WITH DETAILS --- + # TODO: Add SMS logic here return jsonify({ "status": "success", "message": "Alert saved", - "webex_room_id": room_id, + "webex_room_id": webex_room_id, "webex_status": webex_status }), 200 @@ -220,7 +291,7 @@ def telemetry(): return jsonify({"error": "Expected a JSON object/dictionary"}), 400 # Stamp UTC timestamp for Node-RED queries - data["received_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat() + data["received_at"] = datetime.now(datetime.timezone.utc).isoformat() try: telemetry_collection.insert_one(data)