friends system (Testing WIP)
This commit is contained in:
@@ -1,4 +1,12 @@
|
|||||||
use crate::{models::app_data::AppData, services::cursor::start_cursor_tracking, state::FDOLL};
|
use crate::{
|
||||||
|
models::app_data::AppData,
|
||||||
|
remotes::friends::{
|
||||||
|
FriendRemote, FriendRequestResponseDto, FriendshipResponseDto, SendFriendRequestDto,
|
||||||
|
UserBasicDto,
|
||||||
|
},
|
||||||
|
services::cursor::start_cursor_tracking,
|
||||||
|
state::{init_app_data, FDOLL},
|
||||||
|
};
|
||||||
use tauri::async_runtime;
|
use tauri::async_runtime;
|
||||||
use tracing_subscriber;
|
use tracing_subscriber;
|
||||||
|
|
||||||
@@ -52,6 +60,87 @@ fn get_app_data() -> Result<AppData, String> {
|
|||||||
return Ok(guard.app_data.clone());
|
return Ok(guard.app_data.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn refresh_app_data() -> Result<AppData, String> {
|
||||||
|
init_app_data().await;
|
||||||
|
let guard = lock_r!(FDOLL);
|
||||||
|
Ok(guard.app_data.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn list_friends() -> Result<Vec<FriendshipResponseDto>, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.get_friends()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn search_users(username: Option<String>) -> Result<Vec<UserBasicDto>, String> {
|
||||||
|
tracing::info!(
|
||||||
|
"Tauri command search_users called with username: {:?}",
|
||||||
|
username
|
||||||
|
);
|
||||||
|
let remote = FriendRemote::new();
|
||||||
|
tracing::info!("FriendRemote instance created for search_users");
|
||||||
|
let result = remote.search_users(username.as_deref()).await;
|
||||||
|
tracing::info!("FriendRemote::search_users result: {:?}", result);
|
||||||
|
result.map_err(|e| {
|
||||||
|
tracing::error!("search_users command error: {}", e);
|
||||||
|
e.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn send_friend_request(
|
||||||
|
request: SendFriendRequestDto,
|
||||||
|
) -> Result<FriendRequestResponseDto, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.send_friend_request(request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn received_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.get_received_requests()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn sent_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.get_sent_requests()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn accept_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.accept_friend_request(&request_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn deny_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.deny_friend_request(&request_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn unfriend(friend_id: String) -> Result<(), String> {
|
||||||
|
FriendRemote::new()
|
||||||
|
.unfriend(&friend_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn quit_app() -> Result<(), String> {
|
fn quit_app() -> Result<(), String> {
|
||||||
let app_handle = get_app_handle();
|
let app_handle = get_app_handle();
|
||||||
@@ -68,6 +157,15 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
start_cursor_tracking,
|
start_cursor_tracking,
|
||||||
get_app_data,
|
get_app_data,
|
||||||
|
refresh_app_data,
|
||||||
|
list_friends,
|
||||||
|
search_users,
|
||||||
|
send_friend_request,
|
||||||
|
received_friend_requests,
|
||||||
|
sent_friend_requests,
|
||||||
|
accept_friend_request,
|
||||||
|
deny_friend_request,
|
||||||
|
unfriend,
|
||||||
quit_app
|
quit_app
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
use reqwest::{Client, Error};
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::{lock_r, services::auth::with_auth, state::FDOLL};
|
use crate::{lock_r, services::auth::with_auth, state::FDOLL};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum RemoteError {
|
||||||
|
#[error("HTTP error: {0}")]
|
||||||
|
Http(#[from] reqwest::Error),
|
||||||
|
#[error("JSON parse error: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserBasicDto {
|
pub struct UserBasicDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub username: String,
|
pub username: Option<String>,
|
||||||
pub picture: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||||
@@ -66,75 +74,334 @@ impl FriendRemote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_friends(&self) -> Result<Vec<FriendshipResponseDto>, Error> {
|
pub async fn get_friends(&self) -> Result<Vec<FriendshipResponseDto>, RemoteError> {
|
||||||
let url = format!("{}/friends", self.base_url);
|
let url = format!("{}/friends", self.base_url);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_friends - Sending GET request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||||
let friends = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::get_friends - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::get_friends - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_friends - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::get_friends - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!("FriendRemote::get_friends - Response body: {}", text);
|
||||||
|
let friends: Vec<FriendshipResponseDto> = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::get_friends - Failed to parse JSON: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_friends - Successfully parsed {} friends",
|
||||||
|
friends.len()
|
||||||
|
);
|
||||||
Ok(friends)
|
Ok(friends)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn search_users(&self, username: Option<&str>) -> Result<Vec<UserBasicDto>, Error> {
|
pub async fn search_users(
|
||||||
|
&self,
|
||||||
|
username: Option<&str>,
|
||||||
|
) -> Result<Vec<UserBasicDto>, RemoteError> {
|
||||||
let mut url = format!("{}/friends/search", self.base_url);
|
let mut url = format!("{}/friends/search", self.base_url);
|
||||||
if let Some(u) = username {
|
if let Some(u) = username {
|
||||||
url.push_str(&format!("?username={}", u));
|
url.push_str(&format!("?username={}", u));
|
||||||
}
|
}
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::search_users - Sending GET request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.get(&url)).await.send().await?;
|
let resp = with_auth(self.client.get(&url)).await.send().await?;
|
||||||
let users = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::search_users - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::search_users - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::search_users - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::search_users - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!("FriendRemote::search_users - Response body: {}", text);
|
||||||
|
let users: Vec<UserBasicDto> = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::search_users - Failed to parse JSON: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::search_users - Successfully parsed {} users",
|
||||||
|
users.len()
|
||||||
|
);
|
||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_friend_request(
|
pub async fn send_friend_request(
|
||||||
&self,
|
&self,
|
||||||
request: SendFriendRequestDto,
|
request: SendFriendRequestDto,
|
||||||
) -> Result<FriendRequestResponseDto, Error> {
|
) -> Result<FriendRequestResponseDto, RemoteError> {
|
||||||
let url = format!("{}/friends/requests", self.base_url);
|
let url = format!("{}/friends/requests", self.base_url);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::send_friend_request - Sending POST request to URL: {} with body: {:?}",
|
||||||
|
url,
|
||||||
|
request
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.post(url))
|
let resp = with_auth(self.client.post(url))
|
||||||
.await
|
.await
|
||||||
.json(&request)
|
.json(&request)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
let req_resp = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::send_friend_request - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::send_friend_request - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::send_friend_request - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::send_friend_request - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::send_friend_request - Response body: {}",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
let req_resp: FriendRequestResponseDto = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::send_friend_request - Failed to parse JSON: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::send_friend_request - Successfully parsed friend request response"
|
||||||
|
);
|
||||||
Ok(req_resp)
|
Ok(req_resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_received_requests(&self) -> Result<Vec<FriendRequestResponseDto>, Error> {
|
pub async fn get_received_requests(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<FriendRequestResponseDto>, RemoteError> {
|
||||||
let url = format!("{}/friends/requests/received", self.base_url);
|
let url = format!("{}/friends/requests/received", self.base_url);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_received_requests - Sending GET request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||||
let requests = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::get_received_requests - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::get_received_requests - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_received_requests - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::get_received_requests - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_received_requests - Response body: {}",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
let requests: Vec<FriendRequestResponseDto> = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::get_received_requests - Failed to parse JSON: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_received_requests - Successfully parsed {} received requests",
|
||||||
|
requests.len()
|
||||||
|
);
|
||||||
Ok(requests)
|
Ok(requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_sent_requests(&self) -> Result<Vec<FriendRequestResponseDto>, Error> {
|
pub async fn get_sent_requests(&self) -> Result<Vec<FriendRequestResponseDto>, RemoteError> {
|
||||||
let url = format!("{}/friends/requests/sent", self.base_url);
|
let url = format!("{}/friends/requests/sent", self.base_url);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_sent_requests - Sending GET request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||||
let requests = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::get_sent_requests - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::get_sent_requests - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_sent_requests - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::get_sent_requests - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!("FriendRemote::get_sent_requests - Response body: {}", text);
|
||||||
|
let requests: Vec<FriendRequestResponseDto> = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::get_sent_requests - Failed to parse JSON: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::get_sent_requests - Successfully parsed {} sent requests",
|
||||||
|
requests.len()
|
||||||
|
);
|
||||||
Ok(requests)
|
Ok(requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn accept_friend_request(
|
pub async fn accept_friend_request(
|
||||||
&self,
|
&self,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<FriendRequestResponseDto, Error> {
|
) -> Result<FriendRequestResponseDto, RemoteError> {
|
||||||
let url = format!("{}/friends/requests/{}/accept", self.base_url, request_id);
|
let url = format!("{}/friends/requests/{}/accept", self.base_url, request_id);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::accept_friend_request - Sending POST request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.post(url)).await.send().await?;
|
let resp = with_auth(self.client.post(url)).await.send().await?;
|
||||||
let req_resp = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::accept_friend_request - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::accept_friend_request - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::accept_friend_request - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::accept_friend_request - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::accept_friend_request - Response body: {}",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
let req_resp: FriendRequestResponseDto = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::accept_friend_request - Failed to parse JSON: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::accept_friend_request - Successfully parsed friend request response"
|
||||||
|
);
|
||||||
Ok(req_resp)
|
Ok(req_resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn deny_friend_request(
|
pub async fn deny_friend_request(
|
||||||
&self,
|
&self,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<FriendRequestResponseDto, Error> {
|
) -> Result<FriendRequestResponseDto, RemoteError> {
|
||||||
let url = format!("{}/friends/requests/{}/deny", self.base_url, request_id);
|
let url = format!("{}/friends/requests/{}/deny", self.base_url, request_id);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::deny_friend_request - Sending POST request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.post(url)).await.send().await?;
|
let resp = with_auth(self.client.post(url)).await.send().await?;
|
||||||
let req_resp = resp.json().await?;
|
tracing::info!(
|
||||||
|
"FriendRemote::deny_friend_request - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let resp = resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::deny_friend_request - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::deny_friend_request - Response status after error_for_status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
let text = resp.text().await.map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::deny_friend_request - Failed to read response text: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::deny_friend_request - Response body: {}",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
let req_resp: FriendRequestResponseDto = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
tracing::error!(
|
||||||
|
"FriendRemote::deny_friend_request - Failed to parse JSON: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::deny_friend_request - Successfully parsed friend request response"
|
||||||
|
);
|
||||||
Ok(req_resp)
|
Ok(req_resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unfriend(&self, friend_id: &str) -> Result<(), Error> {
|
pub async fn unfriend(&self, friend_id: &str) -> Result<(), RemoteError> {
|
||||||
let url = format!("{}/friends/{}", self.base_url, friend_id);
|
let url = format!("{}/friends/{}", self.base_url, friend_id);
|
||||||
|
tracing::info!(
|
||||||
|
"FriendRemote::unfriend - Sending DELETE request to URL: {}",
|
||||||
|
url
|
||||||
|
);
|
||||||
let resp = with_auth(self.client.delete(url)).await.send().await?;
|
let resp = with_auth(self.client.delete(url)).await.send().await?;
|
||||||
resp.error_for_status()?;
|
tracing::info!(
|
||||||
|
"FriendRemote::unfriend - Received response with status: {}",
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
|
resp.error_for_status().map_err(|e| {
|
||||||
|
tracing::error!("FriendRemote::unfriend - HTTP error: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
tracing::info!("FriendRemote::unfriend - Successfully unfriended");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub struct UserProfile {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub picture: Option<String>,
|
|
||||||
pub roles: Vec<String>,
|
pub roles: Vec<String>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
|
|||||||
@@ -1,7 +1,292 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { appData } from "../../../events/app-data";
|
import { appData } from "../../../events/app-data";
|
||||||
|
import type { FriendRequestResponseDto } from "../../../types/bindings/FriendRequestResponseDto.js";
|
||||||
|
import type { FriendshipResponseDto } from "../../../types/bindings/FriendshipResponseDto.js";
|
||||||
|
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto.js";
|
||||||
|
|
||||||
|
let received: FriendRequestResponseDto[] = [];
|
||||||
|
let sent: FriendRequestResponseDto[] = [];
|
||||||
|
|
||||||
|
let loading = {
|
||||||
|
received: false,
|
||||||
|
sent: false,
|
||||||
|
add: false,
|
||||||
|
action: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let error: string | null = null;
|
||||||
|
let searchTerm = "";
|
||||||
|
|
||||||
|
let friends: FriendshipResponseDto[] = [];
|
||||||
|
$: friends = $appData?.friends ?? [];
|
||||||
|
|
||||||
|
type CombinedRequest = {
|
||||||
|
id: string;
|
||||||
|
type: "incoming" | "outgoing";
|
||||||
|
request: FriendRequestResponseDto;
|
||||||
|
};
|
||||||
|
|
||||||
|
let combinedRequests: CombinedRequest[] = [];
|
||||||
|
$: combinedRequests = [
|
||||||
|
...received.map((req) => ({
|
||||||
|
id: `incoming-${req.id}`,
|
||||||
|
type: "incoming" as const,
|
||||||
|
request: req,
|
||||||
|
})),
|
||||||
|
...sent.map((req) => ({
|
||||||
|
id: `outgoing-${req.id}`,
|
||||||
|
type: "outgoing" as const,
|
||||||
|
request: req,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
refreshReceived();
|
||||||
|
refreshSent();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refreshReceived() {
|
||||||
|
loading.received = true;
|
||||||
|
try {
|
||||||
|
received = await invoke("received_friend_requests");
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.received = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshSent() {
|
||||||
|
loading.sent = true;
|
||||||
|
try {
|
||||||
|
sent = await invoke("sent_friend_requests");
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.sent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAccept(id: string) {
|
||||||
|
loading.action = true;
|
||||||
|
try {
|
||||||
|
await invoke("accept_friend_request", { requestId: id });
|
||||||
|
await Promise.all([refreshReceived(), invoke("refresh_app_data")]);
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeny(id: string) {
|
||||||
|
loading.action = true;
|
||||||
|
try {
|
||||||
|
await invoke("deny_friend_request", { requestId: id });
|
||||||
|
await refreshReceived();
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUnfriend(friendId: string) {
|
||||||
|
loading.action = true;
|
||||||
|
try {
|
||||||
|
await invoke("unfriend", { friendId });
|
||||||
|
await invoke("refresh_app_data");
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
searchTerm = "";
|
||||||
|
error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAddFriend() {
|
||||||
|
const term = searchTerm.trim();
|
||||||
|
const sanitizedTerm = term.replace(/^@/, "");
|
||||||
|
const normalizedTerm = sanitizedTerm.toLowerCase();
|
||||||
|
|
||||||
|
if (!sanitizedTerm) {
|
||||||
|
error = "Please enter a username.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.add = true;
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await invoke<UserBasicDto[]>("search_users", {
|
||||||
|
username: sanitizedTerm,
|
||||||
|
});
|
||||||
|
const match = results.find(
|
||||||
|
(user) => user.username?.toLowerCase() === normalizedTerm,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
error = `No user found with username "${sanitizedTerm}".`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await handleSendRequest(match.id);
|
||||||
|
searchTerm = "";
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.add = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSendRequest(receiverId: string) {
|
||||||
|
loading.action = true;
|
||||||
|
try {
|
||||||
|
await invoke("send_friend_request", {
|
||||||
|
request: { receiverId },
|
||||||
|
});
|
||||||
|
await refreshSent();
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
} finally {
|
||||||
|
loading.action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="friends-page flex flex-col gap-2">
|
||||||
<p>{$appData?.user?.name}'s friends</p>
|
{#if error}
|
||||||
|
<div class="text-error text-sm">{error}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<section class="flex flex-col gap-2">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="relative flex-1 input input-bordered input-sm w-full">
|
||||||
|
<input
|
||||||
|
class="pr-20"
|
||||||
|
placeholder="Add a friend"
|
||||||
|
bind:value={searchTerm}
|
||||||
|
on:keydown={(e) => e.key === "Enter" && handleAddFriend()}
|
||||||
|
/>
|
||||||
|
{#if searchTerm.trim().length}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-xs btn-ghost absolute right-1 top-1/2 -translate-y-1/2"
|
||||||
|
on:click={clearSearch}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
disabled={loading.add}
|
||||||
|
on:click={handleAddFriend}
|
||||||
|
>
|
||||||
|
{loading.add ? "Adding..." : "Add"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse bg-base-100 border-base-300 border">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-sm opacity-70 py-2">Friend requests</div>
|
||||||
|
<div class="collapse-content px-2 -mb-2">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{#if loading.received || loading.sent}
|
||||||
|
<p class="text-sm text-base-content/70">Loading requests...</p>
|
||||||
|
{:else if combinedRequests.length === 0}
|
||||||
|
<p class="text-sm text-base-content/70">
|
||||||
|
No pending friend requests.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#each combinedRequests as entry (entry.id)}
|
||||||
|
<div class="card px-3 py-2 bg-base-200/50">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="font-light">
|
||||||
|
{entry.type === "incoming"
|
||||||
|
? entry.request.sender.name
|
||||||
|
: entry.request.receiver.name}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base-content/70">
|
||||||
|
@{entry.type === "incoming"
|
||||||
|
? (entry.request.sender.username ?? "unknown")
|
||||||
|
: (entry.request.receiver.username ?? "unknown")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if entry.type === "incoming"}
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-xs btn-primary"
|
||||||
|
disabled={loading.action}
|
||||||
|
on:click={() => handleAccept(entry.request.id)}
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-xs btn-ghost"
|
||||||
|
disabled={loading.action}
|
||||||
|
on:click={() => handleDeny(entry.request.id)}
|
||||||
|
>
|
||||||
|
Deny
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-xs text-base-content/60 capitalize">
|
||||||
|
{entry.request.status}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="collapse bg-base-100 border-base-300 border">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-sm opacity-70 py-2">Friends</div>
|
||||||
|
<div class="collapse-content px-2 -mb-2">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{#if friends.length === 0}
|
||||||
|
<p class="text-sm text-base-content/70">No friends yet.</p>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#each friends as friend (friend.id)}
|
||||||
|
<div class="card px-3 py-2 bg-base-200/50">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="font-light">{friend.friend.name}</div>
|
||||||
|
<div class="text-xs text-base-content/70">
|
||||||
|
@{friend.friend.username ?? "unknown"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
disabled={loading.action}
|
||||||
|
on:click={() => handleUnfriend(friend.friend.id)}
|
||||||
|
>
|
||||||
|
Unfriend
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { UserProfile } from "./UserProfile";
|
import type { UserProfile } from "./UserProfile.js";
|
||||||
|
import type { FriendshipResponseDto } from "./FriendshipResponseDto.js";
|
||||||
|
|
||||||
export type AppData = { user: UserProfile | null, };
|
export type AppData = {
|
||||||
|
user: UserProfile | null;
|
||||||
|
friends: Array<FriendshipResponseDto> | null;
|
||||||
|
};
|
||||||
|
|||||||
10
src/types/bindings/FriendRequestResponseDto.ts
Normal file
10
src/types/bindings/FriendRequestResponseDto.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { UserBasicDto } from "./UserBasicDto.js";
|
||||||
|
|
||||||
|
export type FriendRequestResponseDto = {
|
||||||
|
id: string;
|
||||||
|
sender: UserBasicDto;
|
||||||
|
receiver: UserBasicDto;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
7
src/types/bindings/FriendshipResponseDto.ts
Normal file
7
src/types/bindings/FriendshipResponseDto.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { UserBasicDto } from "./UserBasicDto.js";
|
||||||
|
|
||||||
|
export type FriendshipResponseDto = {
|
||||||
|
id: string;
|
||||||
|
friend: UserBasicDto;
|
||||||
|
createdAt: string;
|
||||||
|
};
|
||||||
3
src/types/bindings/SendFriendRequestDto.ts
Normal file
3
src/types/bindings/SendFriendRequestDto.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type SendFriendRequestDto = {
|
||||||
|
receiverId: string;
|
||||||
|
};
|
||||||
5
src/types/bindings/UserBasicDto.ts
Normal file
5
src/types/bindings/UserBasicDto.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type UserBasicDto = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
username: string | null;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user