use crate::model; use crate::state; use axum::extract::ws::Message; use rand::distributions::Alphanumeric; use rand::{Rng, thread_rng}; use sqlx::{Row, SqlitePool}; use std::io::Cursor; // Utility functions pub fn generate_token() -> String { thread_rng() .sample_iter(&Alphanumeric) .take(32) .map(char::from) .collect() } pub async fn verify_token(pool: &SqlitePool, token: &str) -> Result, sqlx::Error> { let result = sqlx::query( "SELECT username FROM sessions WHERE token = ? AND expires_at > datetime('now')", ) .bind(token) .fetch_optional(pool) .await?; Ok(result.map(|row| row.get("username"))) } pub async fn mark_messages_as_read( pool: &SqlitePool, from_user: &str, to_user: &str, ) -> Result<(), sqlx::Error> { let result = sqlx::query( "UPDATE messages SET read = TRUE WHERE from_user = ? AND to_user = ? AND read = FALSE AND deleted_for_everyone = FALSE" ) .bind(from_user) .bind(to_user) .execute(pool) .await?; println!( "✅ Marked {} messages as read from {} to {}", result.rows_affected(), from_user, to_user ); Ok(()) } // Process and resize avatar pub async fn process_avatar(data: &[u8]) -> Result, String> { // Load image let img = match image::load_from_memory(data) { Ok(img) => img, Err(_) => return Err("Invalid image format".to_string()), }; // Resize image to 150x150 pixels let resized = img.resize_to_fill(150, 150, image::imageops::FilterType::Lanczos3); // Convert to JPEG let mut jpeg_data = Vec::new(); match resized.write_to(&mut Cursor::new(&mut jpeg_data), image::ImageFormat::Jpeg) { Ok(_) => Ok(jpeg_data), Err(_) => Err("Error processing image".to_string()), } } pub async fn get_message_history( pool: &SqlitePool, username: &str, other_user: &str, ) -> Result, sqlx::Error> { // Если указан конкретный собеседник, помечаем сообщения как прочитанные if !other_user.is_empty() { mark_messages_as_read(pool, other_user, username).await?; } let query = if other_user.is_empty() { // Получаем все сообщения пользователя (исключая удаленные) r#" SELECT id, from_user, to_user, content, timestamp, message_type FROM messages WHERE (from_user = ? OR to_user = ?) AND deleted_for_everyone = FALSE AND ((from_user = ? AND deleted_for_sender = FALSE) OR (to_user = ? AND deleted_for_receiver = FALSE)) ORDER BY timestamp "# } else { // Получаем сообщения только с конкретным пользователем (исключая удаленные) r#" SELECT id, from_user, to_user, content, timestamp, message_type FROM messages WHERE ((from_user = ? AND to_user = ?) OR (from_user = ? AND to_user = ?)) AND deleted_for_everyone = FALSE AND ((from_user = ? AND deleted_for_sender = FALSE) OR (to_user = ? AND deleted_for_receiver = FALSE)) ORDER BY timestamp "# }; let rows = if other_user.is_empty() { sqlx::query(query) .bind(username) .bind(username) .bind(username) .bind(username) .fetch_all(pool) .await? } else { sqlx::query(query) .bind(username) .bind(other_user) .bind(other_user) .bind(username) .bind(username) .bind(username) .fetch_all(pool) .await? }; let mut messages = Vec::new(); for row in rows { let message = model::ChatMessage { id: row.get("id"), from_user: row.get("from_user"), to_user: row.get("to_user"), content: row.get("content"), timestamp: row.get("timestamp"), message_type: row.get("message_type"), }; messages.push(message); } Ok(messages) } pub async fn broadcast_user_list(users: &state::UserMap) { let user_list: Vec = users.read().await.keys().cloned().collect(); let user_list_msg = serde_json::json!({ "type": "user_list", "users": user_list }); for tx in users.read().await.values() { let _ = tx.send(Message::Text(user_list_msg.to_string())); } } pub async fn save_message(pool: &SqlitePool, msg: &model::ChatMessage) -> Result<(), sqlx::Error> { sqlx::query( "INSERT OR REPLACE INTO messages (id, from_user, to_user, content, timestamp, message_type) VALUES (?, ?, ?, ?, ?, ?)", ) .bind(&msg.id) .bind(&msg.from_user) .bind(&msg.to_user) .bind(&msg.content) .bind(msg.timestamp) .bind(&msg.message_type) .execute(pool) .await?; Ok(()) }