diff --git a/Cargo.lock b/Cargo.lock index 037c95a..8336e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -82,6 +88,7 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "rustversion", @@ -151,6 +158,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -185,6 +204,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" @@ -237,6 +262,12 @@ dependencies = [ "inout", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "const-oid" version = "0.9.6" @@ -283,6 +314,34 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -298,6 +357,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -363,6 +428,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -396,18 +470,52 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -566,6 +674,27 @@ dependencies = [ "wasip2", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -848,6 +977,24 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -873,6 +1020,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.82" @@ -892,6 +1048,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.177" @@ -910,7 +1072,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "redox_syscall", ] @@ -997,6 +1159,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -1016,6 +1188,7 @@ dependencies = [ "bcrypt", "chrono", "futures-util", + "image", "rand", "serde", "serde_json", @@ -1024,6 +1197,24 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "uuid", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", ] [[package]] @@ -1120,7 +1311,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -1241,6 +1432,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -1268,6 +1472,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" version = "1.0.42" @@ -1313,13 +1526,33 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1348,7 +1581,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -1388,7 +1621,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -1527,6 +1760,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.11" @@ -1679,7 +1918,7 @@ checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", - "bitflags", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -1722,7 +1961,7 @@ checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", - "bitflags", + "bitflags 2.10.0", "byteorder", "chrono", "crc", @@ -1881,6 +2120,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -1992,7 +2242,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -2179,6 +2429,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2263,6 +2524,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "whoami" version = "1.6.1" @@ -2595,3 +2862,12 @@ dependencies = [ "quote", "syn 2.0.111", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 996d1a5..fe91289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -axum = { version = "0.7", features = ["ws", "json"] } +axum = { version = "0.7", features = ["ws", "json", "multipart"] } tokio = { version = "1.0", features = ["full"] } tower-http = { version = "0.5", features = ["fs"] } serde = { version = "1.0", features = ["derive"] } @@ -15,4 +15,6 @@ sqlx = { version = "0.7", features = ["runtime-tokio-native-tls", "sqlite", "mac futures-util = "0.3" bcrypt = "0.15" rand = "0.8" -chrono = { version = "0.4", features = ["serde"] } \ No newline at end of file +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1.0", features = ["v4"] } +image = "0.24" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9c816a1..c209d09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use axum::{ extract::{ ws::{Message, WebSocket, WebSocketUpgrade}, - State, Json, Query, + State, Json, Query, Multipart, }, response::Response, routing::{get, post, delete}, @@ -13,6 +13,7 @@ use std::{ collections::HashMap, net::SocketAddr, sync::Arc, + path::Path, }; use tokio::sync::{mpsc, RwLock}; use tower_http::services::ServeDir; @@ -21,6 +22,8 @@ use bcrypt::{hash, verify, DEFAULT_COST}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use chrono::{Utc, Duration}; +use uuid::Uuid; +use std::io::Cursor; // Types for shared state type UserMap = Arc>>>; @@ -72,6 +75,7 @@ struct AuthResponse { struct UserProfile { username: String, phone: String, + avatar_url: Option, } #[derive(Debug, Serialize)] @@ -79,6 +83,7 @@ struct ValidateTokenResponse { success: bool, username: Option, phone: Option, + avatar_url: Option, message: Option, } @@ -86,6 +91,7 @@ struct ValidateTokenResponse { struct ChatPartner { username: String, phone: String, + avatar_url: Option, last_message: Option, last_message_time: Option, unread_count: i64, @@ -114,11 +120,21 @@ struct DeleteMessageResponse { message: String, } +#[derive(Debug, Serialize)] +struct UploadAvatarResponse { + success: bool, + avatar_url: Option, + message: String, +} + #[tokio::main] async fn main() -> Result<(), Box> { // Initialize logging tracing_subscriber::fmt::init(); + // Create avatars directory if it doesn't exist + tokio::fs::create_dir_all("web/avatars").await?; + // Initialize database let db_pool = init_db().await?; @@ -139,6 +155,7 @@ async fn main() -> Result<(), Box> { .route("/api/debug-messages", get(debug_messages_handler)) .route("/api/mark-read", post(mark_read_handler)) .route("/api/delete-message", post(delete_message_handler)) + .route("/api/upload-avatar", post(upload_avatar_handler)) .route("/api/profile", get(profile_handler)) .nest_service("/", ServeDir::new("web")) .with_state(state); @@ -163,6 +180,7 @@ async fn init_db() -> Result> { phone TEXT UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, + avatar_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) "#, @@ -391,7 +409,7 @@ async fn validate_token_handler( Ok(Some(username)) => { // Get user details let user_result = sqlx::query( - "SELECT phone FROM users WHERE username = ?" + "SELECT phone, avatar_url FROM users WHERE username = ?" ) .bind(&username) .fetch_optional(&state.db_pool) @@ -400,10 +418,12 @@ async fn validate_token_handler( match user_result { Ok(Some(row)) => { let phone: String = row.get("phone"); + let avatar_url: Option = row.get("avatar_url"); Json(ValidateTokenResponse { success: true, username: Some(username), phone: Some(phone), + avatar_url, message: None, }) } @@ -411,12 +431,14 @@ async fn validate_token_handler( success: false, username: None, phone: None, + avatar_url: None, message: Some("User not found".to_string()), }), Err(_) => Json(ValidateTokenResponse { success: false, username: None, phone: None, + avatar_url: None, message: Some("Database error".to_string()), }), } @@ -425,12 +447,14 @@ async fn validate_token_handler( success: false, username: None, phone: None, + avatar_url: None, message: Some("Invalid or expired token".to_string()), }), Err(_) => Json(ValidateTokenResponse { success: false, username: None, phone: None, + avatar_url: None, message: Some("Token verification error".to_string()), }), } @@ -615,7 +639,7 @@ async fn get_chats_handler( // Получаем информацию о пользователе let user_result = sqlx::query( - "SELECT username, phone FROM users WHERE username = ?" + "SELECT username, phone, avatar_url FROM users WHERE username = ?" ) .bind(&partner_username) .fetch_optional(&state.db_pool) @@ -625,6 +649,7 @@ async fn get_chats_handler( Ok(Some(user_row)) => { let partner_name: String = user_row.get("username"); let phone: String = user_row.get("phone"); + let avatar_url: Option = user_row.get("avatar_url"); // Получаем последнее сообщение let last_message_result = sqlx::query( @@ -684,6 +709,7 @@ async fn get_chats_handler( let chat = ChatPartner { username: partner_name.clone(), phone, + avatar_url, last_message, last_message_time, unread_count, @@ -738,7 +764,7 @@ async fn search_users_handler( let users = sqlx::query( r#" - SELECT username, phone + SELECT username, phone, avatar_url FROM users WHERE (username LIKE ? OR phone LIKE ?) AND username != ? @@ -758,6 +784,7 @@ async fn search_users_handler( results.push(ChatPartner { username: row.get("username"), phone: row.get("phone"), + avatar_url: row.get("avatar_url"), last_message: None, last_message_time: None, unread_count: 0, @@ -939,13 +966,181 @@ async fn delete_message_handler( } } -async fn profile_handler() -> Json { - Json(UserProfile { - username: "user".to_string(), - phone: "+1234567890".to_string(), +// Upload avatar endpoint +async fn upload_avatar_handler( + State(state): State>, + headers: axum::http::HeaderMap, + mut multipart: Multipart, +) -> Json { + // Extract token from header + let token = if let Some(auth_header) = headers.get("authorization") { + auth_header.to_str().unwrap_or("").replace("Bearer ", "") + } else { + return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "No token".to_string(), + }); + }; + + // Get current user from token + let username = match verify_token(&state.db_pool, &token).await { + Ok(Some(user)) => user, + _ => return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "Invalid token".to_string(), + }), + }; + + // Process multipart form + while let Some(field) = multipart.next_field().await.unwrap() { + let field_name = field.name().unwrap_or("").to_string(); + + if field_name == "avatar" { + let data = field.bytes().await.unwrap(); + + // Validate image size (max 5MB) + if data.len() > 5 * 1024 * 1024 { + return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "Image too large (max 5MB)".to_string(), + }); + } + + // Process image + match process_avatar(&data).await { + Ok(processed_data) => { + // Generate unique filename + let filename = format!("{}.jpg", Uuid::new_v4()); + let avatar_path = format!("web/avatars/{}", filename); + + // Save file + if let Err(e) = tokio::fs::write(&avatar_path, &processed_data).await { + eprintln!("Error saving avatar: {}", e); + return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "Error saving image".to_string(), + }); + } + + let avatar_url = format!("/avatars/{}", filename); + + // Update user in database + let result = sqlx::query( + "UPDATE users SET avatar_url = ? WHERE username = ?" + ) + .bind(&avatar_url) + .bind(&username) + .execute(&state.db_pool) + .await; + + match result { + Ok(_) => { + return Json(UploadAvatarResponse { + success: true, + avatar_url: Some(avatar_url), + message: "Avatar uploaded successfully".to_string(), + }); + } + Err(e) => { + eprintln!("Error updating user avatar: {}", e); + return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "Database error".to_string(), + }); + } + } + } + Err(e) => { + return Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: e, + }); + } + } + } + } + + Json(UploadAvatarResponse { + success: false, + avatar_url: None, + message: "No avatar file provided".to_string(), }) } +// Process and resize avatar +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()), + } +} + +async fn profile_handler( + State(state): State>, + headers: axum::http::HeaderMap, +) -> Json { + // Extract token from header + let token = if let Some(auth_header) = headers.get("authorization") { + auth_header.to_str().unwrap_or("").replace("Bearer ", "") + } else { + return Json(UserProfile { + username: "user".to_string(), + phone: "+1234567890".to_string(), + avatar_url: None, + }); + }; + + // Get current user from token + let username = match verify_token(&state.db_pool, &token).await { + Ok(Some(user)) => user, + _ => return Json(UserProfile { + username: "user".to_string(), + phone: "+1234567890".to_string(), + avatar_url: None, + }), + }; + + // Get user details + let user_result = sqlx::query( + "SELECT username, phone, avatar_url FROM users WHERE username = ?" + ) + .bind(&username) + .fetch_optional(&state.db_pool) + .await; + + match user_result { + Ok(Some(row)) => { + Json(UserProfile { + username: row.get("username"), + phone: row.get("phone"), + avatar_url: row.get("avatar_url"), + }) + } + _ => Json(UserProfile { + username: "user".to_string(), + phone: "+1234567890".to_string(), + avatar_url: None, + }), + } +} + // WebSocket handlers async fn websocket_handler( ws: WebSocketUpgrade, diff --git a/web/index.html b/web/index.html index 5a170bc..f14c94a 100644 --- a/web/index.html +++ b/web/index.html @@ -5,96 +5,1081 @@ Secure Chat App + + +
@@ -136,40 +1121,74 @@
-