Spaces:
Sleeping
Sleeping
debug1
Browse files
app.py
CHANGED
|
@@ -17,6 +17,7 @@ from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
|
|
| 17 |
from slack_sdk.oauth.installation_store.async_installation_store import AsyncInstallationStore
|
| 18 |
from slack_sdk.oauth import AuthorizeUrlGenerator
|
| 19 |
from slack_sdk.oauth.installation_store.models import Installation
|
|
|
|
| 20 |
|
| 21 |
# RAG/ML Libraries
|
| 22 |
from sentence_transformers import SentenceTransformer
|
|
@@ -51,6 +52,10 @@ if not all(required_vars):
|
|
| 51 |
missing = [var for var in required_vars if not var]
|
| 52 |
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
# Set HF_TOKEN if provided (helps with authentication for Hub access)
|
| 55 |
if HF_TOKEN:
|
| 56 |
try:
|
|
@@ -62,7 +67,7 @@ if HF_TOKEN:
|
|
| 62 |
# Initialize Supabase client
|
| 63 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 64 |
|
| 65 |
-
# --- Supabase Async Installation Store ---
|
| 66 |
class SupabaseAsyncInstallationStore(AsyncInstallationStore):
|
| 67 |
def __init__(self, supabase_client):
|
| 68 |
self.supabase = supabase_client
|
|
@@ -85,14 +90,14 @@ class SupabaseAsyncInstallationStore(AsyncInstallationStore):
|
|
| 85 |
raise
|
| 86 |
|
| 87 |
async def fetch_installation(self, team_id: str, *, enterprise_id: str | None = None, user_id: str | None = None) -> Installation | None:
|
| 88 |
-
logger.info(f"Fetching installation for team_id: {team_id}, enterprise_id: {enterprise_id}, user_id: {user_id}")
|
| 89 |
try:
|
| 90 |
result = await asyncio.to_thread(
|
| 91 |
lambda: self.supabase.table("installations").select("*").eq("team_id", team_id).execute()
|
| 92 |
)
|
| 93 |
-
logger.info(f"Fetch result for {team_id}: {len(result.data)} rows")
|
| 94 |
if not result.data:
|
| 95 |
-
logger.warning(f"No installation found for team {team_id}")
|
| 96 |
return None
|
| 97 |
|
| 98 |
data = result.data[0]
|
|
@@ -120,20 +125,44 @@ class SupabaseAsyncInstallationStore(AsyncInstallationStore):
|
|
| 120 |
logger.error(f"Failed to delete installation for team {team_id}: {e}")
|
| 121 |
raise
|
| 122 |
|
| 123 |
-
# Initialize installation store
|
| 124 |
-
installation_store =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
# Initialize Bolt Async App
|
| 127 |
app = AsyncApp(
|
| 128 |
signing_secret=SLACK_SIGNING_SECRET,
|
|
|
|
| 129 |
installation_store=installation_store,
|
|
|
|
| 130 |
oauth_settings=AsyncOAuthSettings(
|
| 131 |
client_id=SLACK_CLIENT_ID,
|
| 132 |
client_secret=SLACK_CLIENT_SECRET,
|
| 133 |
scopes=["app_mentions:read", "files:read", "chat:write", "im:read", "im:write", "channels:read"],
|
| 134 |
redirect_uri_path="/slack/oauth/callback",
|
| 135 |
),
|
| 136 |
-
# Removed token for multi-workspace support; add back if single-workspace only
|
| 137 |
)
|
| 138 |
|
| 139 |
api = FastAPI()
|
|
@@ -364,6 +393,7 @@ handler = AsyncSlackRequestHandler(app)
|
|
| 364 |
async def slack_events(request: Request):
|
| 365 |
"""Endpoint for all Slack event subscriptions."""
|
| 366 |
try:
|
|
|
|
| 367 |
return await handler.handle(request)
|
| 368 |
except Exception as e:
|
| 369 |
logger.error(f"Error handling Slack events: {e}")
|
|
@@ -397,9 +427,8 @@ async def install_url():
|
|
| 397 |
client_id=SLACK_CLIENT_ID,
|
| 398 |
scopes=["app_mentions:read", "files:read", "chat:write", "im:read", "im:write", "channels:read"]
|
| 399 |
)
|
| 400 |
-
# NOTE: The redirect_uri should match your Slack App config (e.g., https://yourspace.hf.space/slack/oauth/callback)
|
| 401 |
-
# If needed, add: redirect_uri=os.environ.get("SLACK_REDIRECT_URI")
|
| 402 |
url = generator.generate(state="state")
|
|
|
|
| 403 |
return {"install_url": url}
|
| 404 |
except Exception as e:
|
| 405 |
logger.error(f"Error generating install URL: {e}")
|
|
@@ -409,19 +438,22 @@ async def install_url():
|
|
| 409 |
async def oauth_callback(request: Request):
|
| 410 |
"""Handles the OAuth callback from Slack to complete installation."""
|
| 411 |
try:
|
|
|
|
| 412 |
response = await handler.handle(request)
|
| 413 |
-
logger.info(f"OAuth callback response: {response.status_code if hasattr(response, 'status_code') else 'No status'}")
|
| 414 |
-
# For successful OAuth, return a simple HTML response
|
| 415 |
if hasattr(response, 'status_code') and response.status_code == 200:
|
|
|
|
|
|
|
|
|
|
| 416 |
return HTMLResponse(
|
| 417 |
-
content="<html><body><h1>Installation successful!</h1><p>You can now use the bot in your Slack workspace.</p><p>
|
| 418 |
status_code=200
|
| 419 |
)
|
| 420 |
return response
|
| 421 |
except Exception as e:
|
| 422 |
logger.error(f"OAuth Callback Error: {e}")
|
| 423 |
return HTMLResponse(
|
| 424 |
-
content=f"<html><body><h1>Installation Failed!</h1><p>Error: {str(e)}</p><p>Check logs
|
| 425 |
status_code=500
|
| 426 |
)
|
| 427 |
|
|
@@ -433,7 +465,7 @@ async def debug_installations():
|
|
| 433 |
result = await asyncio.to_thread(
|
| 434 |
lambda: supabase.table("installations").select("*").execute()
|
| 435 |
)
|
| 436 |
-
return {"installations": result.data}
|
| 437 |
except Exception as e:
|
| 438 |
return {"error": str(e)}
|
| 439 |
|
|
|
|
| 17 |
from slack_sdk.oauth.installation_store.async_installation_store import AsyncInstallationStore
|
| 18 |
from slack_sdk.oauth import AuthorizeUrlGenerator
|
| 19 |
from slack_sdk.oauth.installation_store.models import Installation
|
| 20 |
+
from slack_bolt.authorization import AsyncAuthorizeResult # Added for custom authorize
|
| 21 |
|
| 22 |
# RAG/ML Libraries
|
| 23 |
from sentence_transformers import SentenceTransformer
|
|
|
|
| 52 |
missing = [var for var in required_vars if not var]
|
| 53 |
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
| 54 |
|
| 55 |
+
# For single-team mode, require SLACK_BOT_TOKEN
|
| 56 |
+
if not SLACK_BOT_TOKEN:
|
| 57 |
+
raise ValueError("SLACK_BOT_TOKEN is required for single-team mode.")
|
| 58 |
+
|
| 59 |
# Set HF_TOKEN if provided (helps with authentication for Hub access)
|
| 60 |
if HF_TOKEN:
|
| 61 |
try:
|
|
|
|
| 67 |
# Initialize Supabase client
|
| 68 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 69 |
|
| 70 |
+
# --- Supabase Async Installation Store (for multi-team; optional) ---
|
| 71 |
class SupabaseAsyncInstallationStore(AsyncInstallationStore):
|
| 72 |
def __init__(self, supabase_client):
|
| 73 |
self.supabase = supabase_client
|
|
|
|
| 90 |
raise
|
| 91 |
|
| 92 |
async def fetch_installation(self, team_id: str, *, enterprise_id: str | None = None, user_id: str | None = None) -> Installation | None:
|
| 93 |
+
logger.info(f"Fetching installation for team_id: {team_id}, enterprise_id: {enterprise_id}, user_id: {user_id}")
|
| 94 |
try:
|
| 95 |
result = await asyncio.to_thread(
|
| 96 |
lambda: self.supabase.table("installations").select("*").eq("team_id", team_id).execute()
|
| 97 |
)
|
| 98 |
+
logger.info(f"Fetch result for {team_id}: {len(result.data)} rows")
|
| 99 |
if not result.data:
|
| 100 |
+
logger.warning(f"No installation found for team {team_id}")
|
| 101 |
return None
|
| 102 |
|
| 103 |
data = result.data[0]
|
|
|
|
| 125 |
logger.error(f"Failed to delete installation for team {team_id}: {e}")
|
| 126 |
raise
|
| 127 |
|
| 128 |
+
# Initialize installation store (only if multi-team)
|
| 129 |
+
installation_store = None
|
| 130 |
+
USE_MULTI_TEAM = os.environ.get("USE_MULTI_TEAM", "false").lower() == "true" # Set env to 'true' for multi-team
|
| 131 |
+
if USE_MULTI_TEAM:
|
| 132 |
+
installation_store = SupabaseAsyncInstallationStore(supabase)
|
| 133 |
+
logger.info("Using multi-team mode with Supabase store.")
|
| 134 |
+
else:
|
| 135 |
+
logger.info("Using single-team mode with SLACK_BOT_TOKEN.")
|
| 136 |
+
|
| 137 |
+
# Custom authorize for multi-team (falls back to store)
|
| 138 |
+
async def async_authorize(enterprise_id, team_id, user_id):
|
| 139 |
+
logger.info(f"Custom authorize called for enterprise: {enterprise_id}, team: {team_id}, user: {user_id}")
|
| 140 |
+
if not USE_MULTI_TEAM:
|
| 141 |
+
return None # Single-team doesn't need this
|
| 142 |
+
installation = await installation_store.fetch_installation(team_id=team_id, enterprise_id=enterprise_id)
|
| 143 |
+
if installation:
|
| 144 |
+
return AsyncAuthorizeResult(
|
| 145 |
+
enterprise_id=enterprise_id or installation.enterprise_id,
|
| 146 |
+
team_id=team_id,
|
| 147 |
+
user_id=user_id or installation.user_id,
|
| 148 |
+
bot_token=installation.bot_token,
|
| 149 |
+
bot_user_id=installation.bot_user_id,
|
| 150 |
+
)
|
| 151 |
+
logger.error(f"No authorization found for team {team_id}")
|
| 152 |
+
return None
|
| 153 |
|
| 154 |
# Initialize Bolt Async App
|
| 155 |
app = AsyncApp(
|
| 156 |
signing_secret=SLACK_SIGNING_SECRET,
|
| 157 |
+
token=SLACK_BOT_TOKEN if not USE_MULTI_TEAM else None, # Single-team: use token
|
| 158 |
installation_store=installation_store,
|
| 159 |
+
authorize=async_authorize if USE_MULTI_TEAM else None, # Multi-team: custom authorize
|
| 160 |
oauth_settings=AsyncOAuthSettings(
|
| 161 |
client_id=SLACK_CLIENT_ID,
|
| 162 |
client_secret=SLACK_CLIENT_SECRET,
|
| 163 |
scopes=["app_mentions:read", "files:read", "chat:write", "im:read", "im:write", "channels:read"],
|
| 164 |
redirect_uri_path="/slack/oauth/callback",
|
| 165 |
),
|
|
|
|
| 166 |
)
|
| 167 |
|
| 168 |
api = FastAPI()
|
|
|
|
| 393 |
async def slack_events(request: Request):
|
| 394 |
"""Endpoint for all Slack event subscriptions."""
|
| 395 |
try:
|
| 396 |
+
logger.info("Received Slack event request")
|
| 397 |
return await handler.handle(request)
|
| 398 |
except Exception as e:
|
| 399 |
logger.error(f"Error handling Slack events: {e}")
|
|
|
|
| 427 |
client_id=SLACK_CLIENT_ID,
|
| 428 |
scopes=["app_mentions:read", "files:read", "chat:write", "im:read", "im:write", "channels:read"]
|
| 429 |
)
|
|
|
|
|
|
|
| 430 |
url = generator.generate(state="state")
|
| 431 |
+
logger.info(f"Generated install URL: {url}")
|
| 432 |
return {"install_url": url}
|
| 433 |
except Exception as e:
|
| 434 |
logger.error(f"Error generating install URL: {e}")
|
|
|
|
| 438 |
async def oauth_callback(request: Request):
|
| 439 |
"""Handles the OAuth callback from Slack to complete installation."""
|
| 440 |
try:
|
| 441 |
+
logger.info("OAuth callback received")
|
| 442 |
response = await handler.handle(request)
|
| 443 |
+
logger.info(f"OAuth callback response: {response.status_code if hasattr(response, 'status_code') else 'No status'}")
|
|
|
|
| 444 |
if hasattr(response, 'status_code') and response.status_code == 200:
|
| 445 |
+
# In single-team, no need to save; for multi, it should auto-save via store
|
| 446 |
+
if USE_MULTI_TEAM:
|
| 447 |
+
logger.info("Multi-team install: Check Supabase for new data.")
|
| 448 |
return HTMLResponse(
|
| 449 |
+
content=f"<html><body><h1>Installation successful!</h1><p>You can now use the bot in your Slack workspace.</p><p>Mode: {'Single-team (no store needed)' if not USE_MULTI_TEAM else 'Multi-team (check Supabase)'}</p><p><a href='/debug/installations'>Debug Installations</a></p></body></html>",
|
| 450 |
status_code=200
|
| 451 |
)
|
| 452 |
return response
|
| 453 |
except Exception as e:
|
| 454 |
logger.error(f"OAuth Callback Error: {e}")
|
| 455 |
return HTMLResponse(
|
| 456 |
+
content=f"<html><body><h1>Installation Failed!</h1><p>Error: {str(e)}</p><p>Check logs. If multi-team, ensure Supabase table exists.</p></body></html>",
|
| 457 |
status_code=500
|
| 458 |
)
|
| 459 |
|
|
|
|
| 465 |
result = await asyncio.to_thread(
|
| 466 |
lambda: supabase.table("installations").select("*").execute()
|
| 467 |
)
|
| 468 |
+
return {"installations": result.data, "mode": "multi-team" if USE_MULTI_TEAM else "single-team"}
|
| 469 |
except Exception as e:
|
| 470 |
return {"error": str(e)}
|
| 471 |
|