updated fully featured text messaging

This commit is contained in:
wt committed 2025-11-23 20:37:16 +07:00
1 parent d9d3dd2de9
commit ded35ae796
4 files changed
+1926 -163

No files matched your search

Generated
+284 -8
View File
@@ -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",
]
+4 -2
View File
@@ -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"] }
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.0", features = ["v4"] }
image = "0.24"
+203 -8
View File
@@ -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<RwLock<HashMap<String, mpsc::UnboundedSender<Message>>>>;
@@ -72,6 +75,7 @@ struct AuthResponse {
struct UserProfile {
username: String,
phone: String,
avatar_url: Option<String>,
}
#[derive(Debug, Serialize)]
@@ -79,6 +83,7 @@ struct ValidateTokenResponse {
success: bool,
username: Option<String>,
phone: Option<String>,
avatar_url: Option<String>,
message: Option<String>,
}
@@ -86,6 +91,7 @@ struct ValidateTokenResponse {
struct ChatPartner {
username: String,
phone: String,
avatar_url: Option<String>,
last_message: Option<String>,
last_message_time: Option<i64>,
unread_count: i64,
@@ -114,11 +120,21 @@ struct DeleteMessageResponse {
message: String,
}
#[derive(Debug, Serialize)]
struct UploadAvatarResponse {
success: bool,
avatar_url: Option<String>,
message: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
.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<SqlitePool, Box<dyn std::error::Error>> {
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<String> = 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<String> = 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<UserProfile> {
Json(UserProfile {
username: "user".to_string(),
phone: "+1234567890".to_string(),
// Upload avatar endpoint
async fn upload_avatar_handler(
State(state): State<Arc<AppState>>,
headers: axum::http::HeaderMap,
mut multipart: Multipart,
) -> Json<UploadAvatarResponse> {
// 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<Vec<u8>, 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<Arc<AppState>>,
headers: axum::http::HeaderMap,
) -> Json<UserProfile> {
// 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,
+1435 -145
View File
File diff suppressed because it is too large. Load diff