mirror of https://github.com/buster-so/buster.git
Add Slack channels retrieval handler for Slack integration
Co-authored-by: dallin <dallin@buster.so>
This commit is contained in:
parent
0a51562ca6
commit
4448806d02
|
@ -9,6 +9,7 @@ pub mod messages;
|
|||
pub mod metrics;
|
||||
pub mod organizations;
|
||||
pub mod search;
|
||||
pub mod slack;
|
||||
pub mod users;
|
||||
pub mod utils;
|
||||
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use axum::extract::{Extension, Query};
|
||||
use axum::response::Json;
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use database::{
|
||||
pool::get_pg_pool,
|
||||
vault::read_secret,
|
||||
};
|
||||
use middleware::auth::Auth;
|
||||
use server_shared::error::AppError;
|
||||
use sharing::validate_identity_has_permission;
|
||||
|
||||
use reqwest::Client;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GetChannelsQuery {
|
||||
pub integration_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ChannelInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub is_private: bool,
|
||||
pub is_member: bool,
|
||||
pub num_members: Option<i32>,
|
||||
pub topic: Option<String>,
|
||||
pub purpose: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GetChannelsResponse {
|
||||
pub channels: Vec<ChannelInfo>,
|
||||
}
|
||||
|
||||
// Slack API response structures
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SlackChannelsResponse {
|
||||
ok: bool,
|
||||
channels: Vec<SlackChannel>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SlackChannel {
|
||||
id: String,
|
||||
name: String,
|
||||
is_private: bool,
|
||||
is_member: bool,
|
||||
num_members: Option<i32>,
|
||||
topic: Option<SlackTopicPurpose>,
|
||||
purpose: Option<SlackTopicPurpose>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SlackTopicPurpose {
|
||||
value: String,
|
||||
}
|
||||
|
||||
pub async fn get_channels_handler(
|
||||
Extension(auth): Extension<Auth>,
|
||||
Query(query): Query<GetChannelsQuery>,
|
||||
) -> Result<Json<GetChannelsResponse>, AppError> {
|
||||
let user = auth.user()?;
|
||||
|
||||
// Get a database connection
|
||||
let pool = get_pg_pool();
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(anyhow!("Failed to get connection: {}", e)))?;
|
||||
|
||||
// First, check if the integration exists and belongs to the user's organization
|
||||
let integration = dsl::slack_integrations
|
||||
.filter(slack_integrations::id.eq(&query.integration_id))
|
||||
.filter(slack_integrations::deleted_at.is_null())
|
||||
.filter(slack_integrations::status.eq("active"))
|
||||
.select((
|
||||
slack_integrations::id,
|
||||
slack_integrations::organization_id,
|
||||
slack_integrations::token_vault_key,
|
||||
))
|
||||
.first::<(Uuid, Uuid, Option<String>)>(&mut conn)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
diesel::result::Error::NotFound => {
|
||||
AppError::not_found("Slack integration not found or not active")
|
||||
}
|
||||
_ => AppError::internal_error(anyhow!("Failed to fetch integration: {}", e)),
|
||||
})?;
|
||||
|
||||
let (integration_id, organization_id, token_vault_key) = integration;
|
||||
|
||||
// Check if user has permission to access this integration
|
||||
validate_identity_has_permission(
|
||||
&user.id,
|
||||
&organization_id,
|
||||
"read",
|
||||
"slack_integration",
|
||||
&integration_id,
|
||||
&mut conn,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::permission_denied(format!(
|
||||
"You don't have permission to view this Slack integration: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get the token from vault
|
||||
let token_vault_key = token_vault_key
|
||||
.ok_or_else(|| AppError::internal_error(anyhow!("No token found for integration")))?;
|
||||
|
||||
// Read the token from vault using the integration ID as the key
|
||||
let token = read_secret(&integration_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to read Slack token from vault: {}", e);
|
||||
AppError::internal_error(anyhow!("Failed to retrieve Slack token"))
|
||||
})?;
|
||||
|
||||
// Parse the token JSON to get the bot token
|
||||
let token_data: HashMap<String, serde_json::Value> = serde_json::from_str(&token)
|
||||
.map_err(|e| {
|
||||
error!("Failed to parse token data: {}", e);
|
||||
AppError::internal_error(anyhow!("Invalid token format"))
|
||||
})?;
|
||||
|
||||
let bot_token = token_data
|
||||
.get("access_token")
|
||||
.or_else(|| token_data.get("bot_token"))
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| AppError::internal_error(anyhow!("Bot token not found in credentials")))?;
|
||||
|
||||
// Call Slack API to get channels
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
.get("https://slack.com/api/conversations.list")
|
||||
.header("Authorization", format!("Bearer {}", bot_token))
|
||||
.query(&[
|
||||
("types", "public_channel"),
|
||||
("exclude_archived", "true"),
|
||||
("limit", "1000"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to call Slack API: {}", e);
|
||||
AppError::internal_error(anyhow!("Failed to fetch channels from Slack"))
|
||||
})?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
error!("Slack API returned error status: {}", response.status());
|
||||
return Err(AppError::internal_error(anyhow!(
|
||||
"Slack API returned error: {}",
|
||||
response.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let slack_response: SlackChannelsResponse = response.json().await.map_err(|e| {
|
||||
error!("Failed to parse Slack response: {}", e);
|
||||
AppError::internal_error(anyhow!("Failed to parse Slack response"))
|
||||
})?;
|
||||
|
||||
if !slack_response.ok {
|
||||
let error_msg = slack_response
|
||||
.error
|
||||
.unwrap_or_else(|| "Unknown error".to_string());
|
||||
error!("Slack API returned error: {}", error_msg);
|
||||
return Err(AppError::internal_error(anyhow!(
|
||||
"Slack API error: {}",
|
||||
error_msg
|
||||
)));
|
||||
}
|
||||
|
||||
// Convert Slack channels to our response format
|
||||
let channels: Vec<ChannelInfo> = slack_response
|
||||
.channels
|
||||
.into_iter()
|
||||
.map(|channel| ChannelInfo {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
is_private: channel.is_private,
|
||||
is_member: channel.is_member,
|
||||
num_members: channel.num_members,
|
||||
topic: channel.topic.map(|t| t.value),
|
||||
purpose: channel.purpose.map(|p| p.value),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(GetChannelsResponse { channels }))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod get_channels_handler;
|
Loading…
Reference in New Issue