mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +08:00
feat: add hyperswitch ai chats table (#8831)
Co-authored-by: Apoorv Dixit <apoorv.dixit@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d93e73dd83
commit
8ed3f7dbf2
@@ -1259,6 +1259,7 @@ allow_connected_merchants = false # Enable or disable connected merchant account
|
||||
[chat]
|
||||
enabled = false # Enable or disable chat features
|
||||
hyperswitch_ai_host = "http://0.0.0.0:8000" # Hyperswitch ai workflow host
|
||||
encryption_key = "" # Key to encrypt and decrypt chats
|
||||
|
||||
[proxy_status_mapping]
|
||||
proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response
|
||||
|
||||
@@ -420,6 +420,7 @@ redis_ttl_buffer_in_seconds=300 # buffer time in seconds to be added to redis tt
|
||||
[chat]
|
||||
enabled = false # Enable or disable chat features
|
||||
hyperswitch_ai_host = "http://0.0.0.0:8000" # Hyperswitch ai workflow host
|
||||
encryption_key = "" # Key to encrypt and decrypt chats
|
||||
|
||||
[proxy_status_mapping]
|
||||
proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response
|
||||
|
||||
@@ -1370,6 +1370,7 @@ version = "HOSTNAME"
|
||||
[chat]
|
||||
enabled = false
|
||||
hyperswitch_ai_host = "http://0.0.0.0:8000"
|
||||
encryption_key = ""
|
||||
|
||||
[proxy_status_mapping]
|
||||
proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response
|
||||
|
||||
@@ -1227,6 +1227,7 @@ allow_connected_merchants = true
|
||||
[chat]
|
||||
enabled = false
|
||||
hyperswitch_ai_host = "http://0.0.0.0:8000"
|
||||
encryption_key = ""
|
||||
|
||||
[authentication_providers]
|
||||
click_to_pay = {connector_list = "adyen, cybersource, trustpay"}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use common_utils::id_type;
|
||||
use masking::Secret;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct ChatRequest {
|
||||
pub message: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct ChatResponse {
|
||||
pub response: Secret<serde_json::Value>,
|
||||
pub merchant_id: id_type::MerchantId,
|
||||
@@ -16,3 +17,32 @@ pub struct ChatResponse {
|
||||
#[serde(skip_serializing)]
|
||||
pub row_count: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ChatListRequest {
|
||||
pub merchant_id: Option<id_type::MerchantId>,
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ChatConversation {
|
||||
pub id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
pub merchant_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub org_id: Option<String>,
|
||||
pub role_id: Option<String>,
|
||||
pub user_query: Secret<String>,
|
||||
pub response: Secret<serde_json::Value>,
|
||||
pub database_query: Option<String>,
|
||||
pub interaction_status: Option<String>,
|
||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||
pub created_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ChatListResponse {
|
||||
pub conversations: Vec<ChatConversation>,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||
|
||||
use crate::chat::{ChatRequest, ChatResponse};
|
||||
use crate::chat::{ChatListRequest, ChatListResponse, ChatRequest, ChatResponse};
|
||||
|
||||
common_utils::impl_api_event_type!(Chat, (ChatRequest, ChatResponse));
|
||||
common_utils::impl_api_event_type!(
|
||||
Chat,
|
||||
(ChatRequest, ChatResponse, ChatListRequest, ChatListResponse)
|
||||
);
|
||||
|
||||
@@ -197,3 +197,9 @@ pub const REQUEST_TIME_OUT: u64 = 30;
|
||||
|
||||
/// API client request timeout for ai service (in seconds)
|
||||
pub const REQUEST_TIME_OUT_FOR_AI_SERVICE: u64 = 120;
|
||||
|
||||
/// Default limit for list operations (can be used across different entities)
|
||||
pub const DEFAULT_LIST_LIMIT: i64 = 100;
|
||||
|
||||
/// Default offset for list operations (can be used across different entities)
|
||||
pub const DEFAULT_LIST_OFFSET: i64 = 0;
|
||||
|
||||
49
crates/diesel_models/src/hyperswitch_ai_interaction.rs
Normal file
49
crates/diesel_models/src/hyperswitch_ai_interaction.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use common_utils::encryption::Encryption;
|
||||
use diesel::{self, Identifiable, Insertable, Queryable, Selectable};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::schema::hyperswitch_ai_interaction;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Deserialize,
|
||||
Identifiable,
|
||||
Queryable,
|
||||
Selectable,
|
||||
Serialize,
|
||||
router_derive::DebugAsDisplay,
|
||||
)]
|
||||
#[diesel(table_name = hyperswitch_ai_interaction, primary_key(id, created_at), check_for_backend(diesel::pg::Pg))]
|
||||
pub struct HyperswitchAiInteraction {
|
||||
pub id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
pub merchant_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub org_id: Option<String>,
|
||||
pub role_id: Option<String>,
|
||||
pub user_query: Option<Encryption>,
|
||||
pub response: Option<Encryption>,
|
||||
pub database_query: Option<String>,
|
||||
pub interaction_status: Option<String>,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
|
||||
#[diesel(table_name = hyperswitch_ai_interaction)]
|
||||
pub struct HyperswitchAiInteractionNew {
|
||||
pub id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub user_id: Option<String>,
|
||||
pub merchant_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub org_id: Option<String>,
|
||||
pub role_id: Option<String>,
|
||||
pub user_query: Option<Encryption>,
|
||||
pub response: Option<Encryption>,
|
||||
pub database_query: Option<String>,
|
||||
pub interaction_status: Option<String>,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
}
|
||||
@@ -23,6 +23,7 @@ pub mod file;
|
||||
pub mod fraud_check;
|
||||
pub mod generic_link;
|
||||
pub mod gsm;
|
||||
pub mod hyperswitch_ai_interaction;
|
||||
#[cfg(feature = "kv_store")]
|
||||
pub mod kv;
|
||||
pub mod locker_mock_up;
|
||||
@@ -69,10 +70,11 @@ pub type StorageResult<T> = error_stack::Result<T, errors::DatabaseError>;
|
||||
pub type PgPooledConn = async_bb8_diesel::Connection<diesel::PgConnection>;
|
||||
pub use self::{
|
||||
address::*, api_keys::*, callback_mapper::*, cards_info::*, configs::*, customers::*,
|
||||
dispute::*, ephemeral_key::*, events::*, file::*, generic_link::*, locker_mock_up::*,
|
||||
mandate::*, merchant_account::*, merchant_connector_account::*, payment_attempt::*,
|
||||
payment_intent::*, payment_method::*, payout_attempt::*, payouts::*, process_tracker::*,
|
||||
refund::*, reverse_lookup::*, user_authentication_method::*,
|
||||
dispute::*, ephemeral_key::*, events::*, file::*, generic_link::*,
|
||||
hyperswitch_ai_interaction::*, locker_mock_up::*, mandate::*, merchant_account::*,
|
||||
merchant_connector_account::*, payment_attempt::*, payment_intent::*, payment_method::*,
|
||||
payout_attempt::*, payouts::*, process_tracker::*, refund::*, reverse_lookup::*,
|
||||
user_authentication_method::*,
|
||||
};
|
||||
/// The types and implementations provided by this module are required for the schema generated by
|
||||
/// `diesel_cli` 2.0 to work with the types defined in Rust code. This is because
|
||||
|
||||
@@ -21,6 +21,7 @@ pub mod fraud_check;
|
||||
pub mod generic_link;
|
||||
pub mod generics;
|
||||
pub mod gsm;
|
||||
pub mod hyperswitch_ai_interaction;
|
||||
pub mod locker_mock_up;
|
||||
pub mod mandate;
|
||||
pub mod merchant_account;
|
||||
|
||||
32
crates/diesel_models/src/query/hyperswitch_ai_interaction.rs
Normal file
32
crates/diesel_models/src/query/hyperswitch_ai_interaction.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use diesel::{associations::HasTable, ExpressionMethods};
|
||||
|
||||
use crate::{
|
||||
hyperswitch_ai_interaction::{HyperswitchAiInteraction, HyperswitchAiInteractionNew},
|
||||
query::generics,
|
||||
schema::hyperswitch_ai_interaction::dsl,
|
||||
PgPooledConn, StorageResult,
|
||||
};
|
||||
|
||||
impl HyperswitchAiInteractionNew {
|
||||
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<HyperswitchAiInteraction> {
|
||||
generics::generic_insert(conn, self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl HyperswitchAiInteraction {
|
||||
pub async fn filter_by_optional_merchant_id(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: Option<&common_utils::id_type::MerchantId>,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
|
||||
conn,
|
||||
dsl::merchant_id.eq(merchant_id.cloned()),
|
||||
Some(limit),
|
||||
Some(offset),
|
||||
Some(dsl::created_at.desc()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,12 @@ mod composite_key {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl CompositeKey for <schema::hyperswitch_ai_interaction::table as diesel::Table>::PrimaryKey {
|
||||
type UK = schema::hyperswitch_ai_interaction::dsl::id;
|
||||
fn get_local_unique_key(&self) -> Self::UK {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl CompositeKey for <schema_v2::incremental_authorization::table as diesel::Table>::PrimaryKey {
|
||||
type UK = schema_v2::incremental_authorization::dsl::authorization_id;
|
||||
fn get_local_unique_key(&self) -> Self::UK {
|
||||
@@ -139,6 +145,7 @@ impl_get_primary_key_for_composite!(
|
||||
schema::customers::table,
|
||||
schema::blocklist::table,
|
||||
schema::incremental_authorization::table,
|
||||
schema::hyperswitch_ai_interaction::table,
|
||||
schema_v2::incremental_authorization::table,
|
||||
schema_v2::blocklist::table
|
||||
);
|
||||
|
||||
@@ -632,6 +632,62 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
hyperswitch_ai_interaction (id, created_at) {
|
||||
#[max_length = 64]
|
||||
id -> Varchar,
|
||||
#[max_length = 64]
|
||||
session_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
user_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
role_id -> Nullable<Varchar>,
|
||||
user_query -> Nullable<Bytea>,
|
||||
response -> Nullable<Bytea>,
|
||||
database_query -> Nullable<Text>,
|
||||
#[max_length = 64]
|
||||
interaction_status -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
hyperswitch_ai_interaction_default (id, created_at) {
|
||||
#[max_length = 64]
|
||||
id -> Varchar,
|
||||
#[max_length = 64]
|
||||
session_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
user_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
role_id -> Nullable<Varchar>,
|
||||
user_query -> Nullable<Bytea>,
|
||||
response -> Nullable<Bytea>,
|
||||
database_query -> Nullable<Text>,
|
||||
#[max_length = 64]
|
||||
interaction_status -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@@ -1667,6 +1723,8 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
fraud_check,
|
||||
gateway_status_map,
|
||||
generic_link,
|
||||
hyperswitch_ai_interaction,
|
||||
hyperswitch_ai_interaction_default,
|
||||
incremental_authorization,
|
||||
locker_mock_up,
|
||||
mandate,
|
||||
|
||||
@@ -646,6 +646,62 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
hyperswitch_ai_interaction (id, created_at) {
|
||||
#[max_length = 64]
|
||||
id -> Varchar,
|
||||
#[max_length = 64]
|
||||
session_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
user_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
role_id -> Nullable<Varchar>,
|
||||
user_query -> Nullable<Bytea>,
|
||||
response -> Nullable<Bytea>,
|
||||
database_query -> Nullable<Text>,
|
||||
#[max_length = 64]
|
||||
interaction_status -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
hyperswitch_ai_interaction_default (id, created_at) {
|
||||
#[max_length = 64]
|
||||
id -> Varchar,
|
||||
#[max_length = 64]
|
||||
session_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
user_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
role_id -> Nullable<Varchar>,
|
||||
user_query -> Nullable<Bytea>,
|
||||
response -> Nullable<Bytea>,
|
||||
database_query -> Nullable<Text>,
|
||||
#[max_length = 64]
|
||||
interaction_status -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@@ -1622,6 +1678,8 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
fraud_check,
|
||||
gateway_status_map,
|
||||
generic_link,
|
||||
hyperswitch_ai_interaction,
|
||||
hyperswitch_ai_interaction_default,
|
||||
incremental_authorization,
|
||||
locker_mock_up,
|
||||
mandate,
|
||||
|
||||
@@ -12,4 +12,5 @@ pub struct HyperswitchAiDataRequest {
|
||||
pub profile_id: id_type::ProfileId,
|
||||
pub org_id: id_type::OrganizationId,
|
||||
pub query: GetDataMessage,
|
||||
pub entity_type: common_enums::EntityType,
|
||||
}
|
||||
|
||||
@@ -298,6 +298,29 @@ impl SecretsHandler for settings::UserAuthMethodSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretsHandler for settings::ChatSettings {
|
||||
async fn convert_to_raw_secret(
|
||||
value: SecretStateContainer<Self, SecuredSecret>,
|
||||
secret_management_client: &dyn SecretManagementInterface,
|
||||
) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
|
||||
let chat_settings = value.get_inner();
|
||||
|
||||
let encryption_key = if chat_settings.enabled {
|
||||
secret_management_client
|
||||
.get_secret(chat_settings.encryption_key.clone())
|
||||
.await?
|
||||
} else {
|
||||
chat_settings.encryption_key.clone()
|
||||
};
|
||||
|
||||
Ok(value.transition_state(|chat_settings| Self {
|
||||
encryption_key,
|
||||
..chat_settings
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretsHandler for settings::NetworkTokenizationService {
|
||||
async fn convert_to_raw_secret(
|
||||
@@ -450,9 +473,14 @@ pub(crate) async fn fetch_raw_secrets(
|
||||
})
|
||||
.await;
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let chat = settings::ChatSettings::convert_to_raw_secret(conf.chat, secret_management_client)
|
||||
.await
|
||||
.expect("Failed to decrypt chat configs");
|
||||
|
||||
Settings {
|
||||
server: conf.server,
|
||||
chat: conf.chat,
|
||||
chat,
|
||||
master_database,
|
||||
redis: conf.redis,
|
||||
log: conf.log,
|
||||
|
||||
@@ -71,7 +71,7 @@ pub struct Settings<S: SecretState> {
|
||||
pub server: Server,
|
||||
pub proxy: Proxy,
|
||||
pub env: Env,
|
||||
pub chat: ChatSettings,
|
||||
pub chat: SecretStateContainer<ChatSettings, S>,
|
||||
pub master_database: SecretStateContainer<Database, S>,
|
||||
#[cfg(feature = "olap")]
|
||||
pub replica_database: SecretStateContainer<Database, S>,
|
||||
@@ -207,6 +207,7 @@ pub struct Platform {
|
||||
pub struct ChatSettings {
|
||||
pub enabled: bool,
|
||||
pub hyperswitch_ai_host: String,
|
||||
pub encryption_key: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
@@ -1048,8 +1049,7 @@ impl Settings<SecuredSecret> {
|
||||
self.secrets.get_inner().validate()?;
|
||||
self.locker.validate()?;
|
||||
self.connectors.validate("connectors")?;
|
||||
self.chat.validate()?;
|
||||
|
||||
self.chat.get_inner().validate()?;
|
||||
self.cors.validate()?;
|
||||
|
||||
self.scheduler
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
use api_models::chat as chat_api;
|
||||
use common_utils::{
|
||||
consts,
|
||||
crypto::{DecodeMessage, GcmAes256},
|
||||
errors::CustomResult,
|
||||
request::{Method, RequestBuilder, RequestContent},
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use external_services::http_client;
|
||||
use hyperswitch_domain_models::chat as chat_domain;
|
||||
use router_env::{instrument, logger, tracing};
|
||||
use masking::ExposeInterface;
|
||||
use router_env::{
|
||||
instrument, logger,
|
||||
tracing::{self, Instrument},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::errors::chat::ChatErrors,
|
||||
routes::{app::SessionStateInfo, SessionState},
|
||||
services::{authentication as auth, ApplicationResponse},
|
||||
services::{authentication as auth, authorization::roles, ApplicationResponse},
|
||||
utils,
|
||||
};
|
||||
|
||||
#[instrument(skip_all, fields(?session_id))]
|
||||
@@ -22,15 +28,34 @@ pub async fn get_data_from_hyperswitch_ai_workflow(
|
||||
req: chat_api::ChatRequest,
|
||||
session_id: Option<&str>,
|
||||
) -> CustomResult<ApplicationResponse<chat_api::ChatResponse>, ChatErrors> {
|
||||
let url = format!("{}/webhook", state.conf.chat.hyperswitch_ai_host);
|
||||
let request_id = state.get_request_id();
|
||||
let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
|
||||
&state,
|
||||
&user_from_token.role_id,
|
||||
&user_from_token.org_id,
|
||||
user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
)
|
||||
.await
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to retrieve role information")?;
|
||||
let url = format!(
|
||||
"{}/webhook",
|
||||
state.conf.chat.get_inner().hyperswitch_ai_host
|
||||
);
|
||||
let request_id = state
|
||||
.get_request_id()
|
||||
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
|
||||
let request_body = chat_domain::HyperswitchAiDataRequest {
|
||||
query: chat_domain::GetDataMessage {
|
||||
message: req.message,
|
||||
message: req.message.clone(),
|
||||
},
|
||||
org_id: user_from_token.org_id,
|
||||
merchant_id: user_from_token.merchant_id,
|
||||
profile_id: user_from_token.profile_id,
|
||||
org_id: user_from_token.org_id.clone(),
|
||||
merchant_id: user_from_token.merchant_id.clone(),
|
||||
profile_id: user_from_token.profile_id.clone(),
|
||||
entity_type: role_info.get_entity_type(),
|
||||
};
|
||||
logger::info!("Request for AI service: {:?}", request_body);
|
||||
|
||||
@@ -38,11 +63,9 @@ pub async fn get_data_from_hyperswitch_ai_workflow(
|
||||
.method(Method::Post)
|
||||
.url(&url)
|
||||
.attach_default_headers()
|
||||
.header(consts::X_REQUEST_ID, &request_id)
|
||||
.set_body(RequestContent::Json(Box::new(request_body.clone())));
|
||||
|
||||
if let Some(request_id) = request_id {
|
||||
request_builder = request_builder.header(consts::X_REQUEST_ID, &request_id);
|
||||
}
|
||||
if let Some(session_id) = session_id {
|
||||
request_builder = request_builder.header(consts::X_CHAT_SESSION_ID, session_id);
|
||||
}
|
||||
@@ -57,10 +80,132 @@ pub async fn get_data_from_hyperswitch_ai_workflow(
|
||||
.await
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Error when sending request to AI service")?
|
||||
.json::<_>()
|
||||
.json::<chat_api::ChatResponse>()
|
||||
.await
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Error when deserializing response from AI service")?;
|
||||
|
||||
Ok(ApplicationResponse::Json(response))
|
||||
let response_to_return = response.clone();
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let new_hyperswitch_ai_interaction = utils::chat::construct_hyperswitch_ai_interaction(
|
||||
&state,
|
||||
&user_from_token,
|
||||
&req,
|
||||
&response,
|
||||
&request_id,
|
||||
)
|
||||
.await;
|
||||
|
||||
match new_hyperswitch_ai_interaction {
|
||||
Ok(interaction) => {
|
||||
let db = state.store.as_ref();
|
||||
if let Err(e) = db.insert_hyperswitch_ai_interaction(interaction).await {
|
||||
logger::error!("Failed to insert hyperswitch_ai_interaction: {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logger::error!("Failed to construct hyperswitch_ai_interaction: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
.in_current_span(),
|
||||
);
|
||||
|
||||
Ok(ApplicationResponse::Json(response_to_return))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list_chat_conversations(
|
||||
state: SessionState,
|
||||
user_from_token: auth::UserFromToken,
|
||||
req: chat_api::ChatListRequest,
|
||||
) -> CustomResult<ApplicationResponse<chat_api::ChatListResponse>, ChatErrors> {
|
||||
let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
|
||||
&state,
|
||||
&user_from_token.role_id,
|
||||
&user_from_token.org_id,
|
||||
user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
)
|
||||
.await
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to retrieve role information")?;
|
||||
|
||||
if !role_info.is_internal() {
|
||||
return Err(error_stack::Report::new(ChatErrors::UnauthorizedAccess)
|
||||
.attach_printable("Only internal roles are allowed for this operation"));
|
||||
}
|
||||
|
||||
let db = state.store.as_ref();
|
||||
let hyperswitch_ai_interactions = db
|
||||
.list_hyperswitch_ai_interactions(
|
||||
req.merchant_id,
|
||||
req.limit.unwrap_or(consts::DEFAULT_LIST_LIMIT),
|
||||
req.offset.unwrap_or(consts::DEFAULT_LIST_OFFSET),
|
||||
)
|
||||
.await
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Error when fetching hyperswitch_ai_interactions")?;
|
||||
|
||||
let encryption_key = state.conf.chat.get_inner().encryption_key.clone().expose();
|
||||
let key = match hex::decode(&encryption_key) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
router_env::logger::error!("Failed to decode encryption key: {}", e);
|
||||
encryption_key.as_bytes().to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
let mut conversations = Vec::new();
|
||||
|
||||
for interaction in hyperswitch_ai_interactions {
|
||||
let user_query_encrypted = interaction
|
||||
.user_query
|
||||
.ok_or(ChatErrors::InternalServerError)
|
||||
.attach_printable("Missing user_query field in hyperswitch_ai_interaction")?;
|
||||
let response_encrypted = interaction
|
||||
.response
|
||||
.ok_or(ChatErrors::InternalServerError)
|
||||
.attach_printable("Missing response field in hyperswitch_ai_interaction")?;
|
||||
|
||||
let user_query_decrypted_bytes = GcmAes256
|
||||
.decode_message(&key, user_query_encrypted.into_inner())
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to decrypt user query")?;
|
||||
|
||||
let response_decrypted_bytes = GcmAes256
|
||||
.decode_message(&key, response_encrypted.into_inner())
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to decrypt response")?;
|
||||
|
||||
let user_query_decrypted = String::from_utf8(user_query_decrypted_bytes)
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to convert decrypted user query to string")?;
|
||||
|
||||
let response_decrypted = serde_json::from_slice(&response_decrypted_bytes)
|
||||
.change_context(ChatErrors::InternalServerError)
|
||||
.attach_printable("Failed to deserialize decrypted response")?;
|
||||
|
||||
conversations.push(chat_api::ChatConversation {
|
||||
id: interaction.id,
|
||||
session_id: interaction.session_id,
|
||||
user_id: interaction.user_id,
|
||||
merchant_id: interaction.merchant_id,
|
||||
profile_id: interaction.profile_id,
|
||||
org_id: interaction.org_id,
|
||||
role_id: interaction.role_id,
|
||||
user_query: user_query_decrypted.into(),
|
||||
response: response_decrypted,
|
||||
database_query: interaction.database_query,
|
||||
interaction_status: interaction.interaction_status,
|
||||
created_at: interaction.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(ApplicationResponse::Json(chat_api::ChatListResponse {
|
||||
conversations,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ pub enum ChatErrors {
|
||||
MissingConfigError,
|
||||
#[error("Chat response deserialization failed")]
|
||||
ChatResponseDeserializationFailed,
|
||||
#[error("Unauthorized access")]
|
||||
UnauthorizedAccess,
|
||||
}
|
||||
|
||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ChatErrors {
|
||||
@@ -22,6 +24,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::ChatResponseDeserializationFailed => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 2, self.get_error_message(), None))
|
||||
}
|
||||
Self::UnauthorizedAccess => {
|
||||
AER::Unauthorized(ApiError::new(sub_code, 3, self.get_error_message(), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +37,7 @@ impl ChatErrors {
|
||||
Self::InternalServerError => "Something went wrong".to_string(),
|
||||
Self::MissingConfigError => "Missing webhook url".to_string(),
|
||||
Self::ChatResponseDeserializationFailed => "Failed to parse chat response".to_string(),
|
||||
Self::UnauthorizedAccess => "Not authorized to access the resource".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub mod fraud_check;
|
||||
pub mod generic_link;
|
||||
pub mod gsm;
|
||||
pub mod health_check;
|
||||
pub mod hyperswitch_ai_interaction;
|
||||
pub mod kafka_store;
|
||||
pub mod locker_mock_up;
|
||||
pub mod mandate;
|
||||
@@ -137,6 +138,7 @@ pub trait StorageInterface:
|
||||
+ user::sample_data::BatchSampleDataInterface
|
||||
+ health_check::HealthCheckDbInterface
|
||||
+ user_authentication_method::UserAuthenticationMethodInterface
|
||||
+ hyperswitch_ai_interaction::HyperswitchAiInteractionInterface
|
||||
+ authentication::AuthenticationInterface
|
||||
+ generic_link::GenericLinkInterface
|
||||
+ relay::RelayInterface
|
||||
|
||||
123
crates/router/src/db/hyperswitch_ai_interaction.rs
Normal file
123
crates/router/src/db/hyperswitch_ai_interaction.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use diesel_models::hyperswitch_ai_interaction as storage;
|
||||
use error_stack::report;
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use super::MockDb;
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
services::Store,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait HyperswitchAiInteractionInterface {
|
||||
async fn insert_hyperswitch_ai_interaction(
|
||||
&self,
|
||||
hyperswitch_ai_interaction: storage::HyperswitchAiInteractionNew,
|
||||
) -> CustomResult<storage::HyperswitchAiInteraction, errors::StorageError>;
|
||||
|
||||
async fn list_hyperswitch_ai_interactions(
|
||||
&self,
|
||||
merchant_id: Option<common_utils::id_type::MerchantId>,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> CustomResult<Vec<storage::HyperswitchAiInteraction>, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl HyperswitchAiInteractionInterface for Store {
|
||||
#[instrument(skip_all)]
|
||||
async fn insert_hyperswitch_ai_interaction(
|
||||
&self,
|
||||
hyperswitch_ai_interaction: storage::HyperswitchAiInteractionNew,
|
||||
) -> CustomResult<storage::HyperswitchAiInteraction, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
hyperswitch_ai_interaction
|
||||
.insert(&conn)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn list_hyperswitch_ai_interactions(
|
||||
&self,
|
||||
merchant_id: Option<common_utils::id_type::MerchantId>,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> CustomResult<Vec<storage::HyperswitchAiInteraction>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::HyperswitchAiInteraction::filter_by_optional_merchant_id(
|
||||
&conn,
|
||||
merchant_id.as_ref(),
|
||||
limit,
|
||||
offset,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl HyperswitchAiInteractionInterface for MockDb {
|
||||
async fn insert_hyperswitch_ai_interaction(
|
||||
&self,
|
||||
hyperswitch_ai_interaction: storage::HyperswitchAiInteractionNew,
|
||||
) -> CustomResult<storage::HyperswitchAiInteraction, errors::StorageError> {
|
||||
let mut hyperswitch_ai_interactions = self.hyperswitch_ai_interactions.lock().await;
|
||||
let hyperswitch_ai_interaction = storage::HyperswitchAiInteraction {
|
||||
id: hyperswitch_ai_interaction.id,
|
||||
session_id: hyperswitch_ai_interaction.session_id,
|
||||
user_id: hyperswitch_ai_interaction.user_id,
|
||||
merchant_id: hyperswitch_ai_interaction.merchant_id,
|
||||
profile_id: hyperswitch_ai_interaction.profile_id,
|
||||
org_id: hyperswitch_ai_interaction.org_id,
|
||||
role_id: hyperswitch_ai_interaction.role_id,
|
||||
user_query: hyperswitch_ai_interaction.user_query,
|
||||
response: hyperswitch_ai_interaction.response,
|
||||
database_query: hyperswitch_ai_interaction.database_query,
|
||||
interaction_status: hyperswitch_ai_interaction.interaction_status,
|
||||
created_at: hyperswitch_ai_interaction.created_at,
|
||||
};
|
||||
hyperswitch_ai_interactions.push(hyperswitch_ai_interaction.clone());
|
||||
Ok(hyperswitch_ai_interaction)
|
||||
}
|
||||
|
||||
async fn list_hyperswitch_ai_interactions(
|
||||
&self,
|
||||
merchant_id: Option<common_utils::id_type::MerchantId>,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> CustomResult<Vec<storage::HyperswitchAiInteraction>, errors::StorageError> {
|
||||
let hyperswitch_ai_interactions = self.hyperswitch_ai_interactions.lock().await;
|
||||
|
||||
let offset_usize = offset.try_into().unwrap_or_else(|_| {
|
||||
common_utils::consts::DEFAULT_LIST_OFFSET
|
||||
.try_into()
|
||||
.unwrap_or(usize::MIN)
|
||||
});
|
||||
|
||||
let limit_usize = limit.try_into().unwrap_or_else(|_| {
|
||||
common_utils::consts::DEFAULT_LIST_LIMIT
|
||||
.try_into()
|
||||
.unwrap_or(usize::MAX)
|
||||
});
|
||||
|
||||
let filtered_interactions: Vec<storage::HyperswitchAiInteraction> =
|
||||
hyperswitch_ai_interactions
|
||||
.iter()
|
||||
.filter(
|
||||
|interaction| match (merchant_id.as_ref(), &interaction.merchant_id) {
|
||||
(Some(merchant_id), Some(interaction_merchant_id)) => {
|
||||
interaction_merchant_id == &merchant_id.get_string_repr().to_owned()
|
||||
}
|
||||
(None, _) => true,
|
||||
_ => false,
|
||||
},
|
||||
)
|
||||
.skip(offset_usize)
|
||||
.take(limit_usize)
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(filtered_interactions)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ use time::PrimitiveDateTime;
|
||||
use super::{
|
||||
dashboard_metadata::DashboardMetadataInterface,
|
||||
ephemeral_key::ClientSecretInterface,
|
||||
hyperswitch_ai_interaction::HyperswitchAiInteractionInterface,
|
||||
role::RoleInterface,
|
||||
user::{sample_data::BatchSampleDataInterface, theme::ThemeInterface, UserInterface},
|
||||
user_authentication_method::UserAuthenticationMethodInterface,
|
||||
@@ -4127,6 +4128,29 @@ impl UserAuthenticationMethodInterface for KafkaStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl HyperswitchAiInteractionInterface for KafkaStore {
|
||||
async fn insert_hyperswitch_ai_interaction(
|
||||
&self,
|
||||
hyperswitch_ai_interaction: storage::HyperswitchAiInteractionNew,
|
||||
) -> CustomResult<storage::HyperswitchAiInteraction, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.insert_hyperswitch_ai_interaction(hyperswitch_ai_interaction)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_hyperswitch_ai_interactions(
|
||||
&self,
|
||||
merchant_id: Option<id_type::MerchantId>,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> CustomResult<Vec<storage::HyperswitchAiInteraction>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.list_hyperswitch_ai_interactions(merchant_id, limit, offset)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ThemeInterface for KafkaStore {
|
||||
async fn insert_theme(
|
||||
|
||||
@@ -2343,12 +2343,16 @@ pub struct Chat;
|
||||
impl Chat {
|
||||
pub fn server(state: AppState) -> Scope {
|
||||
let mut route = web::scope("/chat").app_data(web::Data::new(state.clone()));
|
||||
if state.conf.chat.enabled {
|
||||
if state.conf.chat.get_inner().enabled {
|
||||
route = route.service(
|
||||
web::scope("/ai").service(
|
||||
web::resource("/data")
|
||||
.route(web::post().to(chat::get_data_from_hyperswitch_ai_workflow)),
|
||||
),
|
||||
web::scope("/ai")
|
||||
.service(
|
||||
web::resource("/data")
|
||||
.route(web::post().to(chat::get_data_from_hyperswitch_ai_workflow)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/list").route(web::get().to(chat::get_all_conversations)),
|
||||
),
|
||||
);
|
||||
}
|
||||
route
|
||||
|
||||
@@ -45,3 +45,24 @@ pub async fn get_data_from_hyperswitch_ai_workflow(
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_all_conversations(
|
||||
state: web::Data<AppState>,
|
||||
http_req: HttpRequest,
|
||||
payload: web::Query<chat_api::ChatListRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::ListAllChatInteractions;
|
||||
Box::pin(api::server_wrap(
|
||||
flow.clone(),
|
||||
state,
|
||||
&http_req,
|
||||
payload.into_inner(),
|
||||
|state, user: auth::UserFromToken, payload, _| {
|
||||
chat_core::list_chat_conversations(state, user, payload)
|
||||
},
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::ListAllThemesInLineage
|
||||
| Flow::CloneConnector => Self::User,
|
||||
|
||||
Flow::GetDataFromHyperswitchAiFlow => Self::AiWorkflow,
|
||||
Flow::GetDataFromHyperswitchAiFlow | Flow::ListAllChatInteractions => Self::AiWorkflow,
|
||||
|
||||
Flow::ListRolesV2
|
||||
| Flow::ListInvitableRolesAtEntityLevel
|
||||
|
||||
@@ -21,6 +21,7 @@ pub mod file;
|
||||
pub mod fraud_check;
|
||||
pub mod generic_link;
|
||||
pub mod gsm;
|
||||
pub mod hyperswitch_ai_interaction;
|
||||
#[cfg(feature = "kv_store")]
|
||||
pub mod kv;
|
||||
pub mod locker_mock_up;
|
||||
@@ -75,8 +76,9 @@ pub use self::{
|
||||
blocklist_fingerprint::*, blocklist_lookup::*, business_profile::*, callback_mapper::*,
|
||||
capture::*, cards_info::*, configs::*, customers::*, dashboard_metadata::*, dispute::*,
|
||||
dynamic_routing_stats::*, ephemeral_key::*, events::*, file::*, fraud_check::*,
|
||||
generic_link::*, gsm::*, locker_mock_up::*, mandate::*, merchant_account::*,
|
||||
merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*,
|
||||
process_tracker::*, refund::*, reverse_lookup::*, role::*, routing_algorithm::*,
|
||||
subscription::*, unified_translations::*, user::*, user_authentication_method::*, user_role::*,
|
||||
generic_link::*, gsm::*, hyperswitch_ai_interaction::*, locker_mock_up::*, mandate::*,
|
||||
merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*,
|
||||
payment_method::*, process_tracker::*, refund::*, reverse_lookup::*, role::*,
|
||||
routing_algorithm::*, subscription::*, unified_translations::*, user::*,
|
||||
user_authentication_method::*, user_role::*,
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub use diesel_models::hyperswitch_ai_interaction::*;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod chat;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod connector_onboarding;
|
||||
pub mod currency;
|
||||
|
||||
70
crates/router/src/utils/chat.rs
Normal file
70
crates/router/src/utils/chat.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use api_models::chat as chat_api;
|
||||
use common_utils::{type_name, types::keymanager::Identifier};
|
||||
use diesel_models::hyperswitch_ai_interaction::{
|
||||
HyperswitchAiInteraction, HyperswitchAiInteractionNew,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
|
||||
use masking::ExposeInterface;
|
||||
|
||||
use crate::{
|
||||
core::errors::{self, CustomResult},
|
||||
routes::SessionState,
|
||||
services::authentication as auth,
|
||||
};
|
||||
|
||||
pub async fn construct_hyperswitch_ai_interaction(
|
||||
state: &SessionState,
|
||||
user_from_token: &auth::UserFromToken,
|
||||
req: &chat_api::ChatRequest,
|
||||
response: &chat_api::ChatResponse,
|
||||
request_id: &str,
|
||||
) -> CustomResult<HyperswitchAiInteractionNew, errors::ApiErrorResponse> {
|
||||
let encryption_key = state.conf.chat.get_inner().encryption_key.clone().expose();
|
||||
let key = match hex::decode(&encryption_key) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
router_env::logger::error!("Failed to decode encryption key: {}", e);
|
||||
// Fallback to using the string as bytes, which was the previous behavior
|
||||
encryption_key.as_bytes().to_vec()
|
||||
}
|
||||
};
|
||||
let encrypted_user_query = crypto_operation::<String, masking::WithType>(
|
||||
&state.into(),
|
||||
type_name!(HyperswitchAiInteraction),
|
||||
CryptoOperation::Encrypt(req.message.clone()),
|
||||
Identifier::Merchant(user_from_token.merchant_id.clone()),
|
||||
&key,
|
||||
)
|
||||
.await
|
||||
.and_then(|val| val.try_into_operation())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to encrypt user query")?;
|
||||
|
||||
let encrypted_response = crypto_operation::<serde_json::Value, masking::WithType>(
|
||||
&state.into(),
|
||||
type_name!(HyperswitchAiInteraction),
|
||||
CryptoOperation::Encrypt(response.response.clone()),
|
||||
Identifier::Merchant(user_from_token.merchant_id.clone()),
|
||||
&key,
|
||||
)
|
||||
.await
|
||||
.and_then(|val| val.try_into_operation())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to encrypt response")?;
|
||||
|
||||
Ok(HyperswitchAiInteractionNew {
|
||||
id: request_id.to_owned(),
|
||||
session_id: Some(request_id.to_string()),
|
||||
user_id: Some(user_from_token.user_id.clone()),
|
||||
merchant_id: Some(user_from_token.merchant_id.get_string_repr().to_string()),
|
||||
profile_id: Some(user_from_token.profile_id.get_string_repr().to_string()),
|
||||
org_id: Some(user_from_token.org_id.get_string_repr().to_string()),
|
||||
role_id: Some(user_from_token.role_id.clone()),
|
||||
user_query: Some(encrypted_user_query.into()),
|
||||
response: Some(encrypted_response.into()),
|
||||
database_query: response.query_executed.clone().map(|q| q.expose()),
|
||||
interaction_status: Some(response.status.clone()),
|
||||
created_at: common_utils::date_time::now(),
|
||||
})
|
||||
}
|
||||
@@ -357,6 +357,8 @@ pub enum Flow {
|
||||
GsmRuleDelete,
|
||||
/// Get data from embedded flow
|
||||
GetDataFromHyperswitchAiFlow,
|
||||
// List all chat interactions
|
||||
ListAllChatInteractions,
|
||||
/// User Sign Up
|
||||
UserSignUp,
|
||||
/// User Sign Up
|
||||
|
||||
@@ -65,6 +65,8 @@ pub struct MockDb {
|
||||
pub user_authentication_methods:
|
||||
Arc<Mutex<Vec<store::user_authentication_method::UserAuthenticationMethod>>>,
|
||||
pub themes: Arc<Mutex<Vec<store::user::theme::Theme>>>,
|
||||
pub hyperswitch_ai_interactions:
|
||||
Arc<Mutex<Vec<store::hyperswitch_ai_interaction::HyperswitchAiInteraction>>>,
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
@@ -113,6 +115,7 @@ impl MockDb {
|
||||
user_key_store: Default::default(),
|
||||
user_authentication_methods: Default::default(),
|
||||
themes: Default::default(),
|
||||
hyperswitch_ai_interactions: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -830,8 +830,7 @@ billing_connectors_which_requires_invoice_sync_call = "recurly"
|
||||
[chat]
|
||||
enabled = false
|
||||
hyperswitch_ai_host = "http://0.0.0.0:8000"
|
||||
|
||||
|
||||
encryption_key = ""
|
||||
|
||||
[revenue_recovery.card_config.amex]
|
||||
max_retries_per_day = 20
|
||||
@@ -847,4 +846,4 @@ max_retry_count_for_thirty_day = 20
|
||||
|
||||
[revenue_recovery.card_config.discover]
|
||||
max_retries_per_day = 20
|
||||
max_retry_count_for_thirty_day = 20
|
||||
max_retry_count_for_thirty_day = 20
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
-- CASCADE will automatically drop all child partitions
|
||||
DROP TABLE IF EXISTS hyperswitch_ai_interaction CASCADE;
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE hyperswitch_ai_interaction (
|
||||
id VARCHAR(64) NOT NULL,
|
||||
session_id VARCHAR(64),
|
||||
user_id VARCHAR(64),
|
||||
merchant_id VARCHAR(64),
|
||||
profile_id VARCHAR(64),
|
||||
org_id VARCHAR(64),
|
||||
role_id VARCHAR(64),
|
||||
user_query BYTEA,
|
||||
response BYTEA,
|
||||
database_query TEXT,
|
||||
interaction_status VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
PRIMARY KEY (id, created_at)
|
||||
) PARTITION BY RANGE (created_at);
|
||||
|
||||
-- Create a default partition
|
||||
CREATE TABLE hyperswitch_ai_interaction_default
|
||||
PARTITION OF hyperswitch_ai_interaction DEFAULT;
|
||||
|
||||
Reference in New Issue
Block a user