2025-10-12 11:44:35 -03:00
|
|
|
use chrono::Utc;
|
|
|
|
|
use diesel::prelude::*;
|
2025-10-11 20:41:52 -03:00
|
|
|
use diesel::PgConnection;
|
2025-10-13 17:43:03 -03:00
|
|
|
use log::{debug, error, info, warn};
|
2025-10-11 20:41:52 -03:00
|
|
|
use redis::Client;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2025-10-12 11:44:35 -03:00
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
use std::error::Error;
|
2025-10-06 10:30:17 -03:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
use crate::shared::models::UserSession;
|
|
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
#[derive(Clone, Serialize, Deserialize)]
|
2025-10-12 11:44:35 -03:00
|
|
|
pub struct SessionData {
|
2025-10-11 20:41:52 -03:00
|
|
|
pub id: Uuid,
|
|
|
|
|
pub user_id: Option<Uuid>,
|
|
|
|
|
pub data: String,
|
|
|
|
|
}
|
2025-10-06 10:30:17 -03:00
|
|
|
|
|
|
|
|
pub struct SessionManager {
|
2025-10-12 11:44:35 -03:00
|
|
|
conn: PgConnection,
|
|
|
|
|
sessions: HashMap<Uuid, SessionData>,
|
2025-10-11 20:41:52 -03:00
|
|
|
waiting_for_input: HashSet<Uuid>,
|
|
|
|
|
redis: Option<Arc<Client>>,
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SessionManager {
|
2025-10-12 11:44:35 -03:00
|
|
|
pub fn new(conn: PgConnection, redis_client: Option<Arc<Client>>) -> Self {
|
2025-10-11 20:41:52 -03:00
|
|
|
info!("Initializing SessionManager");
|
|
|
|
|
SessionManager {
|
2025-10-12 11:44:35 -03:00
|
|
|
conn,
|
2025-10-11 20:41:52 -03:00
|
|
|
sessions: HashMap::new(),
|
|
|
|
|
waiting_for_input: HashSet::new(),
|
|
|
|
|
redis: redis_client,
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
pub fn provide_input(
|
2025-10-11 12:29:03 -03:00
|
|
|
&mut self,
|
2025-10-06 10:30:17 -03:00
|
|
|
session_id: Uuid,
|
2025-10-11 20:41:52 -03:00
|
|
|
input: String,
|
2025-10-13 17:43:03 -03:00
|
|
|
) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
|
2025-10-11 20:41:52 -03:00
|
|
|
info!(
|
|
|
|
|
"SessionManager.provide_input called for session {}",
|
|
|
|
|
session_id
|
2025-10-11 12:29:03 -03:00
|
|
|
);
|
2025-10-13 17:43:03 -03:00
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
if let Some(sess) = self.sessions.get_mut(&session_id) {
|
|
|
|
|
sess.data = input;
|
2025-10-13 17:43:03 -03:00
|
|
|
self.waiting_for_input.remove(&session_id);
|
|
|
|
|
Ok(Some("user_input".to_string()))
|
2025-10-11 20:41:52 -03:00
|
|
|
} else {
|
2025-10-12 11:44:35 -03:00
|
|
|
let sess = SessionData {
|
2025-10-11 20:41:52 -03:00
|
|
|
id: session_id,
|
|
|
|
|
user_id: None,
|
|
|
|
|
data: input,
|
|
|
|
|
};
|
|
|
|
|
self.sessions.insert(session_id, sess);
|
2025-10-13 17:43:03 -03:00
|
|
|
self.waiting_for_input.remove(&session_id);
|
|
|
|
|
Ok(Some("user_input".to_string()))
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
pub fn is_waiting_for_input(&self, session_id: &Uuid) -> bool {
|
|
|
|
|
self.waiting_for_input.contains(session_id)
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 20:41:52 -03:00
|
|
|
pub fn mark_waiting(&mut self, session_id: Uuid) {
|
|
|
|
|
self.waiting_for_input.insert(session_id);
|
|
|
|
|
info!("Session {} marked as waiting for input", session_id);
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
pub fn get_session_by_id(
|
|
|
|
|
&mut self,
|
|
|
|
|
session_id: Uuid,
|
|
|
|
|
) -> Result<Option<UserSession>, Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions::dsl::*;
|
|
|
|
|
|
|
|
|
|
let result = user_sessions
|
|
|
|
|
.filter(id.eq(session_id))
|
|
|
|
|
.first::<UserSession>(&mut self.conn)
|
|
|
|
|
.optional()?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
pub fn get_user_session(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: Uuid,
|
|
|
|
|
bid: Uuid,
|
|
|
|
|
) -> Result<Option<UserSession>, Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions::dsl::*;
|
|
|
|
|
|
|
|
|
|
let result = user_sessions
|
|
|
|
|
.filter(user_id.eq(uid))
|
|
|
|
|
.filter(bot_id.eq(bid))
|
|
|
|
|
.order(created_at.desc())
|
|
|
|
|
.first::<UserSession>(&mut self.conn)
|
|
|
|
|
.optional()?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
pub fn get_or_create_user_session(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: Uuid,
|
|
|
|
|
bid: Uuid,
|
|
|
|
|
session_title: &str,
|
|
|
|
|
) -> Result<Option<UserSession>, Box<dyn Error + Send + Sync>> {
|
|
|
|
|
if let Some(existing) = self.get_user_session(uid, bid)? {
|
|
|
|
|
debug!("Found existing session: {}", existing.id);
|
|
|
|
|
return Ok(Some(existing));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info!("Creating new session for user {} with bot {}", uid, bid);
|
|
|
|
|
self.create_session(uid, bid, session_title).map(Some)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
pub fn create_session(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: Uuid,
|
|
|
|
|
bid: Uuid,
|
|
|
|
|
session_title: &str,
|
|
|
|
|
) -> Result<UserSession, Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions::dsl::*;
|
2025-10-13 17:43:03 -03:00
|
|
|
use crate::shared::models::users::dsl as users_dsl;
|
2025-10-12 11:44:35 -03:00
|
|
|
|
|
|
|
|
let now = Utc::now();
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
let user_exists: Option<Uuid> = users_dsl::users
|
|
|
|
|
.filter(users_dsl::id.eq(uid))
|
|
|
|
|
.select(users_dsl::id)
|
|
|
|
|
.first(&mut self.conn)
|
|
|
|
|
.optional()?;
|
|
|
|
|
|
|
|
|
|
if user_exists.is_none() {
|
|
|
|
|
warn!(
|
|
|
|
|
"User {} does not exist in database, creating placeholder user",
|
|
|
|
|
uid
|
|
|
|
|
);
|
|
|
|
|
diesel::insert_into(users_dsl::users)
|
|
|
|
|
.values((
|
|
|
|
|
users_dsl::id.eq(uid),
|
|
|
|
|
users_dsl::username.eq(format!("anonymous_{}", rand::random::<u32>())),
|
|
|
|
|
users_dsl::email.eq(format!("anonymous_{}@local", rand::random::<u32>())),
|
|
|
|
|
users_dsl::password_hash.eq("placeholder"),
|
|
|
|
|
users_dsl::is_active.eq(true),
|
|
|
|
|
users_dsl::created_at.eq(now),
|
|
|
|
|
users_dsl::updated_at.eq(now),
|
|
|
|
|
))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
info!("Created placeholder user: {}", uid);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
let inserted: UserSession = diesel::insert_into(user_sessions)
|
|
|
|
|
.values((
|
|
|
|
|
id.eq(Uuid::new_v4()),
|
|
|
|
|
user_id.eq(uid),
|
|
|
|
|
bot_id.eq(bid),
|
|
|
|
|
title.eq(session_title),
|
|
|
|
|
context_data.eq(serde_json::json!({})),
|
2025-10-12 13:27:48 -03:00
|
|
|
answer_mode.eq(0),
|
2025-10-12 11:44:35 -03:00
|
|
|
current_tool.eq(None::<String>),
|
|
|
|
|
created_at.eq(now),
|
|
|
|
|
updated_at.eq(now),
|
|
|
|
|
))
|
|
|
|
|
.returning(UserSession::as_returning())
|
2025-10-13 17:43:03 -03:00
|
|
|
.get_result(&mut self.conn)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("Failed to create session in database: {}", e);
|
|
|
|
|
e
|
|
|
|
|
})?;
|
2025-10-12 11:44:35 -03:00
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
info!("New session created: {}", inserted.id);
|
2025-10-12 11:44:35 -03:00
|
|
|
Ok(inserted)
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
pub fn save_message(
|
|
|
|
|
&mut self,
|
|
|
|
|
sess_id: Uuid,
|
|
|
|
|
uid: Uuid,
|
2025-10-12 15:06:16 -03:00
|
|
|
ro: i32,
|
2025-10-12 11:44:35 -03:00
|
|
|
content: &str,
|
2025-10-12 14:39:23 -03:00
|
|
|
msg_type: i32,
|
2025-10-12 11:44:35 -03:00
|
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::message_history::dsl::*;
|
|
|
|
|
|
|
|
|
|
let next_index = message_history
|
|
|
|
|
.filter(session_id.eq(sess_id))
|
|
|
|
|
.count()
|
2025-10-13 17:43:03 -03:00
|
|
|
.get_result::<i64>(&mut self.conn)
|
|
|
|
|
.unwrap_or(0);
|
2025-10-12 11:44:35 -03:00
|
|
|
|
|
|
|
|
diesel::insert_into(message_history)
|
|
|
|
|
.values((
|
|
|
|
|
id.eq(Uuid::new_v4()),
|
|
|
|
|
session_id.eq(sess_id),
|
|
|
|
|
user_id.eq(uid),
|
2025-10-12 15:06:16 -03:00
|
|
|
role.eq(ro),
|
2025-10-12 11:44:35 -03:00
|
|
|
content_encrypted.eq(content),
|
|
|
|
|
message_type.eq(msg_type),
|
|
|
|
|
message_index.eq(next_index),
|
|
|
|
|
created_at.eq(chrono::Utc::now()),
|
|
|
|
|
))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
debug!(
|
|
|
|
|
"Message saved for session {} with index {}",
|
|
|
|
|
sess_id, next_index
|
|
|
|
|
);
|
2025-10-12 11:44:35 -03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_conversation_history(
|
|
|
|
|
&mut self,
|
2025-10-13 17:43:03 -03:00
|
|
|
sess_id: Uuid,
|
2025-10-12 11:44:35 -03:00
|
|
|
_uid: Uuid,
|
|
|
|
|
) -> Result<Vec<(String, String)>, Box<dyn Error + Send + Sync>> {
|
2025-10-13 17:43:03 -03:00
|
|
|
use crate::shared::models::message_history::dsl::*;
|
2025-10-15 00:01:48 -03:00
|
|
|
use redis::Commands; // Import trait that provides the `get` method
|
|
|
|
|
|
|
|
|
|
let redis_key = format!("context:{}:{}", _uid, sess_id);
|
|
|
|
|
|
|
|
|
|
// Fetch context from Redis and append to history on first retrieval
|
|
|
|
|
let redis_context = if let Some(redis_client) = &self.redis {
|
|
|
|
|
let conn = redis_client
|
|
|
|
|
.get_connection()
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
warn!("Failed to get Redis connection: {}", e);
|
|
|
|
|
e
|
|
|
|
|
})
|
|
|
|
|
.ok();
|
|
|
|
|
|
|
|
|
|
if let Some(mut connection) = conn {
|
|
|
|
|
match connection.get::<_, Option<String>>(&redis_key) {
|
|
|
|
|
Ok(Some(context)) => {
|
|
|
|
|
info!(
|
|
|
|
|
"Retrieved context from Redis for key {}: {} chars",
|
|
|
|
|
redis_key,
|
|
|
|
|
context.len()
|
|
|
|
|
);
|
|
|
|
|
Some(context)
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => {
|
|
|
|
|
debug!("No context found in Redis for key {}", redis_key);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("Failed to retrieve context from Redis: {}", e);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2025-10-13 17:43:03 -03:00
|
|
|
|
|
|
|
|
let messages = message_history
|
|
|
|
|
.filter(session_id.eq(sess_id))
|
|
|
|
|
.order(message_index.asc())
|
|
|
|
|
.select((role, content_encrypted))
|
|
|
|
|
.load::<(i32, String)>(&mut self.conn)?;
|
2025-10-12 11:44:35 -03:00
|
|
|
|
2025-10-15 00:01:48 -03:00
|
|
|
// Build conversation history, inserting Redis context as a system (role 2) message if it exists
|
|
|
|
|
let mut history: Vec<(String, String)> = Vec::new();
|
|
|
|
|
|
|
|
|
|
if let Some(ctx) = redis_context {
|
|
|
|
|
history.push(("system".to_string(), ctx));
|
|
|
|
|
}
|
2025-10-12 11:44:35 -03:00
|
|
|
|
2025-10-15 00:01:48 -03:00
|
|
|
for (other_role, content) in messages {
|
|
|
|
|
let role_str = match other_role {
|
|
|
|
|
0 => "user".to_string(),
|
|
|
|
|
1 => "assistant".to_string(),
|
|
|
|
|
2 => "system".to_string(),
|
|
|
|
|
_ => "unknown".to_string(),
|
|
|
|
|
};
|
|
|
|
|
history.push((role_str, content));
|
|
|
|
|
}
|
2025-10-13 17:43:03 -03:00
|
|
|
Ok(history)
|
2025-10-12 11:44:35 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_user_sessions(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: Uuid,
|
|
|
|
|
) -> Result<Vec<UserSession>, Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions;
|
|
|
|
|
|
|
|
|
|
let sessions = user_sessions::table
|
|
|
|
|
.filter(user_sessions::user_id.eq(uid))
|
|
|
|
|
.order(user_sessions::created_at.desc())
|
|
|
|
|
.load::<UserSession>(&mut self.conn)?;
|
|
|
|
|
|
|
|
|
|
Ok(sessions)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_answer_mode(
|
|
|
|
|
&mut self,
|
|
|
|
|
uid: &str,
|
|
|
|
|
bid: &str,
|
2025-10-12 13:27:48 -03:00
|
|
|
mode: i32,
|
2025-10-12 11:44:35 -03:00
|
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions::dsl::*;
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
let user_uuid = Uuid::parse_str(uid).map_err(|e| {
|
|
|
|
|
warn!("Invalid user ID format: {}", uid);
|
|
|
|
|
e
|
|
|
|
|
})?;
|
|
|
|
|
let bot_uuid = Uuid::parse_str(bid).map_err(|e| {
|
|
|
|
|
warn!("Invalid bot ID format: {}", bid);
|
|
|
|
|
e
|
|
|
|
|
})?;
|
2025-10-12 11:44:35 -03:00
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
let updated_count = diesel::update(
|
2025-10-12 11:44:35 -03:00
|
|
|
user_sessions
|
|
|
|
|
.filter(user_id.eq(user_uuid))
|
|
|
|
|
.filter(bot_id.eq(bot_uuid)),
|
|
|
|
|
)
|
|
|
|
|
.set((answer_mode.eq(mode), updated_at.eq(chrono::Utc::now())))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
|
2025-10-13 17:43:03 -03:00
|
|
|
if updated_count == 0 {
|
|
|
|
|
warn!("No session found for user {} and bot {}", uid, bid);
|
|
|
|
|
} else {
|
|
|
|
|
debug!(
|
|
|
|
|
"Answer mode updated to {} for user {} and bot {}",
|
|
|
|
|
mode, uid, bid
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_user_id(
|
|
|
|
|
&mut self,
|
|
|
|
|
session_id: Uuid,
|
|
|
|
|
new_user_id: Uuid,
|
|
|
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
|
|
|
use crate::shared::models::user_sessions::dsl::*;
|
|
|
|
|
|
|
|
|
|
let updated_count = diesel::update(user_sessions.filter(id.eq(session_id)))
|
|
|
|
|
.set((user_id.eq(new_user_id), updated_at.eq(chrono::Utc::now())))
|
|
|
|
|
.execute(&mut self.conn)?;
|
|
|
|
|
|
|
|
|
|
if updated_count == 0 {
|
|
|
|
|
warn!("No session found with ID: {}", session_id);
|
|
|
|
|
} else {
|
|
|
|
|
info!("Updated session {} to user ID: {}", session_id, new_user_id);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 11:44:35 -03:00
|
|
|
Ok(())
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
}
|