Webex first draft
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
|
||||
class WebexManager:
|
||||
def __init__(self, db_collection, client_id, client_secret, redirect_uri, team_id, user_id):
|
||||
self.db = db_collection
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
self.team_id = team_id
|
||||
self.user_id = user_id
|
||||
self.base_url = "https://webexapis.com/v1"
|
||||
|
||||
def save_tokens(self, token_data: dict):
|
||||
"""Saves or updates Webex OAuth tokens in MongoDB."""
|
||||
now = time.time()
|
||||
doc = {
|
||||
"_id": "webex_credentials",
|
||||
"access_token": token_data.get("access_token"),
|
||||
"refresh_token": token_data.get("refresh_token"),
|
||||
"expires_at": now + token_data.get("expires_in", 43200) - 300, # Subtract 5 mins buffer
|
||||
"refresh_token_expires_at": now + token_data.get("refresh_token_expires_in", 7776000),
|
||||
"updated_at": datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
}
|
||||
self.db.replace_one({"_id": "webex_credentials"}, doc, upsert=True)
|
||||
return doc
|
||||
|
||||
def get_access_token(self) -> str:
|
||||
"""Retrieves a valid access token from MongoDB, auto-refreshing if expired."""
|
||||
token_doc = self.db.find_one({"_id": "webex_credentials"})
|
||||
if not token_doc:
|
||||
raise Exception("No Webex OAuth tokens found. Please complete login at /oauth/login.")
|
||||
|
||||
# Check if access token is expired
|
||||
if time.time() >= token_doc.get("expires_at", 0):
|
||||
current_app.logger.info("Webex access token expired. Refreshing...")
|
||||
return self.refresh_token(token_doc.get("refresh_token"))
|
||||
|
||||
return token_doc.get("access_token")
|
||||
|
||||
def exchange_code(self, code: str) -> dict:
|
||||
"""Exchanges OAuth code for access and refresh tokens."""
|
||||
payload = {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"code": code,
|
||||
"redirect_uri": self.redirect_uri
|
||||
}
|
||||
res = requests.post(f"{self.base_url}/access_token", data=payload)
|
||||
res.raise_for_status()
|
||||
return self.save_tokens(res.json())
|
||||
|
||||
def refresh_token(self, refresh_token: str) -> str:
|
||||
"""Refreshes the Webex access token using the stored refresh_token."""
|
||||
payload = {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"refresh_token": refresh_token
|
||||
}
|
||||
res = requests.post(f"{self.base_url}/access_token", data=payload)
|
||||
res.raise_for_status()
|
||||
new_doc = self.save_tokens(res.json())
|
||||
return new_doc["access_token"]
|
||||
|
||||
def create_support_room(self, client_name: str, client_email: str) -> str:
|
||||
"""Creates a team support room and invites the client and yourself."""
|
||||
access_token = self.get_access_token()
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 1. Create room under team
|
||||
room_res = requests.post(
|
||||
f"{self.base_url}/rooms",
|
||||
headers=headers,
|
||||
json={
|
||||
"title": f"Smartwave Support: {client_name}",
|
||||
"teamId": self.team_id
|
||||
}
|
||||
)
|
||||
room_res.raise_for_status()
|
||||
room_id = room_res.json()["id"]
|
||||
|
||||
# 2. Add Client via email (Webex sends invite if user does not exist)
|
||||
if client_email:
|
||||
requests.post(
|
||||
f"{self.base_url}/memberships",
|
||||
headers=headers,
|
||||
json={"roomId": room_id, "personEmail": client_email}
|
||||
)
|
||||
|
||||
# 3. Add yourself (WEBEX_NINLUC_ID)
|
||||
if self.user_id:
|
||||
requests.post(
|
||||
f"{self.base_url}/memberships",
|
||||
headers=headers,
|
||||
json={"roomId": room_id, "personId": self.user_id}
|
||||
)
|
||||
|
||||
# 4. Optional: Send initial welcome message in the room
|
||||
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."
|
||||
}
|
||||
)
|
||||
|
||||
return room_id
|
||||
+5
-1
@@ -1,4 +1,8 @@
|
||||
|
||||
in `/cloud` folder :
|
||||
|
||||
`flask run --debug`
|
||||
`flask run --debug`
|
||||
|
||||
# Oauth Webex
|
||||
|
||||
[webexapis.com](https://webexapis.com/v1/authorize?client_id=C2a4f544bb4f65503e60b6dceded0de194323a873360b2d06eff5a2232ff46176&response_type=code&redirect_uri=https%3A%2F%2Fsmartwave.matthiasg.dev%2Foauth%2Fcallback&scope=spark-admin%3Abroadworks_subscribers_write%20meeting%3Aadmin_participants_read%20spark%3Awebhooks_read%20spark%3Apeople_write%20spark-admin%3Awholesale_customers_write%20meeting%3Asummaries_write%20spark-admin%3Aworkspace_metrics_read%20cjp%3Atask_read%20spark-admin%3Awholesale_billing_reports_read%20meeting%3Asummaries_read%20spark-admin%3Atelephony_pstn_write%20spark-admin%3Avideo_mesh_api_webhook_write%20spark%3Aplaces_read%20cjp%3Aconfig_write%20meeting%3Aadmin_config_write%20spark-admin%3Arecordings_read%20spark-compliance%3Amessages_read%20cjds%3Aadmin_org_read%20spark-admin%3Aworkspaces_write%20spark-compliance%3Ameetings_write%20spark-admin%3Alocations_write%20meeting%3Aadmin_schedule_write%20identity%3Aplaceonetimepassword_create%20spark-admin%3Aorganizations_write%20spark-admin%3Atelephony_pstn_read%20spark%3Adevices_write%20spark-admin%3Awholesale_sub_partners_write%20cjp%3Aconfig_read%20spark-admin%3Abroadworks_billing_reports_write%20spark-admin%3Acall_qualities_read%20spark%3Aapplications_token%20spark%3Akms%20spark-admin%3Awholesale_sub_partners_read%20spark%3Awebrtc_calling%20spark-admin%3Amessages_read%20application%3Awebhooks_write%20spark-admin%3Awholesale_subscribers_read%20spark-admin%3Acalls_write%20meeting%3Aadmin_config_read%20spark%3Atelephony_config_write%20meeting%3Aparticipants_read%20Identity%3Acontact%20spark-admin%3Awholesale_billing_reports_write%20spark-admin%3Avideo_mesh_api_read%20Identity%3AConfig%20spark-compliance%3Awebhooks_write%20spark-admin%3Aorganizations_read%20cjds%3Aadmin_org_write%20spark-compliance%3Ateam_memberships_read%20meeting%3Aschedules_write%20spark-admin%3Avideo_mesh_api_write%20spark-admin%3Arecordings_write%20identity%3Aorganizations_rw%20identity%3Aorganizations_read%20cloud-contact-center%3Apod_conv%20spark-admin%3Adevices_read%20cjp%3Auser%20cjp%3Aconfig%20spark-admin%3Atelephony_config_write%20spark%3Arecordings_write%20spark-admin%3Abroadworks_enterprises_read%20spark%3Acalls_read%20meeting%3Arecordings_write%20spark%3Adevices_read%20spark-compliance%3Aevents_read%20spark-admin%3Aresource_group_memberships_read%20identity%3Apeople_rw%20spark-admin%3Aresource_group_memberships_write%20application%3Awebhooks_read%20spark-admin%3Ahybrid_connectors_read%20spark-compliance%3Ateams_read%20spark-admin%3Aplaces_write%20spark%3Amcp%20spark-admin%3Alicenses_read%20spark%3Aplaces_write%20webexsquare%3Aadmin%20spark%3Awebhooks_write%20meeting%3Aadmin_preferences_write%20spark%3Aall%20meeting%3Aadmin_preferences_read%20analytics%3Aread_all%20spark-admin%3Apeople_write%20spark%3Aorganizations_read%20spark-admin%3Aplaces_read%20spark-admin%3Awholesale_workspace_write%20spark-compliance%3Ateam_memberships_write%20identity%3Agroups_read%20identity%3Atokens_read%20wxc-dedicateduc%3Aadmin_perfmon_read%20spark-compliance%3Arecordings_read%20spark-admin%3Adevices_write%20spark%3Acalls_write%20Identity%3Aone_time_password%20spark-admin%3Aworkspace_locations_read%20spark-compliance%3Arecordings_write%20spark%3Axapi_commands%20spark-compliance%3Awebhooks_read%20spark-compliance%3Amessages_write%20spark-admin%3Awholesale_customers_read%20spark%3Axsi%20spark-admin%3Areports_read%20meeting%3Aparticipants_write%20spark-admin%3Apeople_read%20meeting%3Aadmin_transcripts_read%20spark-compliance%3Amemberships_read%20spark-admin%3Aresource_groups_read%20meeting%3Arecordings_read%20spark-admin%3Alocations_read%20meeting%3Apreferences_write%20meeting%3Aadmin_recordings_read%20identity%3Atokens_write%20meeting%3Atranscripts_read%20spark-admin%3Amessages_write%20spark%3Axapi_statuses%20spark-admin%3Acalls_read%20spark-admin%3Awholesale_subscribers_write%20spark-admin%3Acalling_cdr_read%20spark%3Arecordings_read%20cjp%3Atask_write%20spark-admin%3Ahybrid_clusters_read%20meeting%3Acontrols_read%20spark-admin%3Aworkspace_locations_write%20spark-admin%3Abroadworks_billing_reports_read%20spark-admin%3Atelephony_config_read%20spark-admin%3Abroadworks_enterprises_write%20meeting%3Aadmin_schedule_read%20spark-compliance%3Amemberships_write%20meeting%3Aschedules_read%20Identity%3AOrganization%20spark-admin%3Aroles_read%20spark-compliance%3Ameetings_read%20meeting%3Apreferences_read%20identity%3Apeople_read%20spark-admin%3Aworkspaces_read%20spark%3Atelephony_config_read%20spark-compliance%3Arooms_read%20spark-admin%3Abroadworks_subscribers_read%20identity%3Agroups_rw%20spark-admin%3Areports_write%20meeting%3Acontrols_write%20audit%3Aevents_read%20meeting%3Aadmin_recordings_write%20spark-compliance%3Arooms_write&state=set_state_here)
|
||||
+79
-7
@@ -5,10 +5,14 @@ import uuid
|
||||
import datetime
|
||||
import sys
|
||||
import json
|
||||
from flask import Flask, request, jsonify, current_app
|
||||
import time
|
||||
import urllib.parse
|
||||
import requests
|
||||
from flask import Flask, request, jsonify, current_app, redirect, url_for
|
||||
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
|
||||
|
||||
@@ -29,9 +33,18 @@ MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:27017/")
|
||||
client = MongoClient(MONGO_URI)
|
||||
db = client["microwave_network_db"]
|
||||
|
||||
client_collection = db["client_data"]
|
||||
cooking_collection = db["cooking_parameters"]
|
||||
telemetry_collection = db["telemetry_data"]
|
||||
alert_collection = db["alert_data"]
|
||||
webex_tokens_collection = db["webex_tokens"] # Collection for Webex OAuth tokens
|
||||
|
||||
# Webex Credentials & Configuration from Environment Variables
|
||||
WEBEX_CLIENT_ID = os.getenv("WEBEX_CLIENT_ID", "YOUR_WEBEX_CLIENT_ID")
|
||||
WEBEX_CLIENT_SECRET = os.getenv("WEBEX_CLIENT_SECRET", "YOUR_WEBEX_CLIENT_SECRET")
|
||||
WEBEX_REDIRECT_URI = os.getenv("WEBEX_REDIRECT_URI", "https://smartwave.matthiasg.dev/oauth/callback")
|
||||
WEBEX_TEAM_ID = os.getenv("WEBEX_TEAM_ID", "YOUR_WEBEX_TEAM_ID")
|
||||
WEBEX_NINLUC_ID = os.getenv("WEBEX_NINLUC_ID", "YOUR_WEBEX_NINLUC_ID")
|
||||
|
||||
# Ensure the camera image storage directory exists when the app starts
|
||||
CAMERA_IMAGE_DIR = "storage/dishPhotos"
|
||||
@@ -41,9 +54,49 @@ os.makedirs(CAMERA_IMAGE_DIR, exist_ok=True)
|
||||
# Classes
|
||||
# ---------------------------------------------------------
|
||||
microwave_cook_planner = MicrowaveCookPlanner()
|
||||
# Instantiate Webex Manager
|
||||
webex_manager = WebexManager(
|
||||
db_collection=webex_tokens_collection,
|
||||
client_id=WEBEX_CLIENT_ID,
|
||||
client_secret=WEBEX_CLIENT_SECRET,
|
||||
redirect_uri=WEBEX_REDIRECT_URI,
|
||||
team_id=WEBEX_TEAM_ID,
|
||||
user_id=WEBEX_NINLUC_ID
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Routes
|
||||
# OAuth Routes
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@app.route("/oauth/login")
|
||||
def oauth_login():
|
||||
"""Redirects developer/admin to Webex for initial OAuth authorization."""
|
||||
scopes = "spark:rooms_write spark:rooms_read spark:memberships_write spark:memberships_read spark:messages_write"
|
||||
params = {
|
||||
"client_id": WEBEX_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"redirect_uri": WEBEX_REDIRECT_URI,
|
||||
"scope": scopes
|
||||
}
|
||||
url = f"https://webexapis.com/v1/authorize?{urllib.parse.urlencode(params)}"
|
||||
return redirect(url)
|
||||
|
||||
@app.route("/oauth/callback")
|
||||
def oauth_callback():
|
||||
"""OAuth redirect endpoint that receives the authorization code."""
|
||||
code = request.args.get("code")
|
||||
if not code:
|
||||
return jsonify({"error": "Missing code parameter"}), 400
|
||||
|
||||
try:
|
||||
webex_manager.exchange_code(code)
|
||||
return jsonify({"status": "success", "message": "Webex tokens stored successfully in MongoDB!"}), 200
|
||||
except Exception as e:
|
||||
current_app.logger.exception("Failed to exchange OAuth code")
|
||||
return jsonify({"error": f"OAuth exchange failed: {str(e)}"}), 500
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Application Routes
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@app.route("/")
|
||||
@@ -133,15 +186,34 @@ async def cooking_params():
|
||||
except Exception as e:
|
||||
current_app.logger.exception("Exception in /cooking-params")
|
||||
|
||||
|
||||
@app.route("/alert", methods=["POST"])
|
||||
def alert():
|
||||
# Save the alert in alert_collection
|
||||
data = request.get_json()
|
||||
data = request.get_json() or {}
|
||||
alert_collection.insert_one(data)
|
||||
|
||||
# TODO : Créer une room webex et envoyer un sms
|
||||
|
||||
return jsonify({"status": "success", "message": "Alert saved"}), 200
|
||||
# Extract client metadata from alert payload (or use fallback values)
|
||||
client_name = data.get("client_name", "Unknown Client")
|
||||
client_email = data.get("client_email")
|
||||
|
||||
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)}"
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": "Alert saved",
|
||||
"webex_room_id": room_id,
|
||||
"webex_status": webex_status
|
||||
}), 200
|
||||
|
||||
|
||||
@app.route("/telemetry", methods=["POST"])
|
||||
def telemetry():
|
||||
|
||||
Reference in New Issue
Block a user