friends system (UI WIP)
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:event:allow-listen",
|
||||
"core:event:allow-unlisten"
|
||||
"core:event:allow-unlisten",
|
||||
"core:window:allow-close"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::remotes::user::UserProfile;
|
||||
use crate::remotes::{friends::FriendshipResponseDto, user::UserProfile};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[ts(export)]
|
||||
pub struct AppData {
|
||||
pub user: Option<UserProfile>,
|
||||
pub friends: Option<Vec<FriendshipResponseDto>>,
|
||||
}
|
||||
|
||||
140
src-tauri/src/remotes/friends.rs
Normal file
140
src-tauri/src/remotes/friends.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use reqwest::{Client, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{lock_r, services::auth::with_auth, state::FDOLL};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct UserBasicDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub picture: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct FriendshipResponseDto {
|
||||
pub id: String,
|
||||
pub friend: UserBasicDto,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct SendFriendRequestDto {
|
||||
pub receiver_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct FriendRequestResponseDto {
|
||||
pub id: String,
|
||||
pub sender: UserBasicDto,
|
||||
pub receiver: UserBasicDto,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
pub struct FriendRemote {
|
||||
pub base_url: String,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl FriendRemote {
|
||||
pub fn new() -> Self {
|
||||
let guard = lock_r!(FDOLL);
|
||||
Self {
|
||||
base_url: guard
|
||||
.app_config
|
||||
.api_base_url
|
||||
.as_ref()
|
||||
.expect("App configuration error")
|
||||
.clone(),
|
||||
client: guard
|
||||
.clients
|
||||
.as_ref()
|
||||
.expect("App configuration error")
|
||||
.http_client
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_friends(&self) -> Result<Vec<FriendshipResponseDto>, Error> {
|
||||
let url = format!("{}/friends", self.base_url);
|
||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||
let friends = resp.json().await?;
|
||||
Ok(friends)
|
||||
}
|
||||
|
||||
pub async fn search_users(&self, username: Option<&str>) -> Result<Vec<UserBasicDto>, Error> {
|
||||
let mut url = format!("{}/friends/search", self.base_url);
|
||||
if let Some(u) = username {
|
||||
url.push_str(&format!("?username={}", u));
|
||||
}
|
||||
let resp = with_auth(self.client.get(&url)).await.send().await?;
|
||||
let users = resp.json().await?;
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub async fn send_friend_request(
|
||||
&self,
|
||||
request: SendFriendRequestDto,
|
||||
) -> Result<FriendRequestResponseDto, Error> {
|
||||
let url = format!("{}/friends/requests", self.base_url);
|
||||
let resp = with_auth(self.client.post(url))
|
||||
.await
|
||||
.json(&request)
|
||||
.send()
|
||||
.await?;
|
||||
let req_resp = resp.json().await?;
|
||||
Ok(req_resp)
|
||||
}
|
||||
|
||||
pub async fn get_received_requests(&self) -> Result<Vec<FriendRequestResponseDto>, Error> {
|
||||
let url = format!("{}/friends/requests/received", self.base_url);
|
||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||
let requests = resp.json().await?;
|
||||
Ok(requests)
|
||||
}
|
||||
|
||||
pub async fn get_sent_requests(&self) -> Result<Vec<FriendRequestResponseDto>, Error> {
|
||||
let url = format!("{}/friends/requests/sent", self.base_url);
|
||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||
let requests = resp.json().await?;
|
||||
Ok(requests)
|
||||
}
|
||||
|
||||
pub async fn accept_friend_request(
|
||||
&self,
|
||||
request_id: &str,
|
||||
) -> Result<FriendRequestResponseDto, Error> {
|
||||
let url = format!("{}/friends/requests/{}/accept", self.base_url, request_id);
|
||||
let resp = with_auth(self.client.post(url)).await.send().await?;
|
||||
let req_resp = resp.json().await?;
|
||||
Ok(req_resp)
|
||||
}
|
||||
|
||||
pub async fn deny_friend_request(
|
||||
&self,
|
||||
request_id: &str,
|
||||
) -> Result<FriendRequestResponseDto, Error> {
|
||||
let url = format!("{}/friends/requests/{}/deny", self.base_url, request_id);
|
||||
let resp = with_auth(self.client.post(url)).await.send().await?;
|
||||
let req_resp = resp.json().await?;
|
||||
Ok(req_resp)
|
||||
}
|
||||
|
||||
pub async fn unfriend(&self, friend_id: &str) -> Result<(), Error> {
|
||||
let url = format!("{}/friends/{}", self.base_url, friend_id);
|
||||
let resp = with_auth(self.client.delete(url)).await.send().await?;
|
||||
resp.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod friends;
|
||||
pub mod user;
|
||||
|
||||
@@ -9,11 +9,22 @@ use crate::{lock_r, services::auth::with_auth, state::FDOLL};
|
||||
#[ts(export)]
|
||||
pub struct UserProfile {
|
||||
pub id: String,
|
||||
pub keycloak_sub: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
pub username: Option<String>,
|
||||
pub picture: Option<String>,
|
||||
pub roles: Vec<String>,
|
||||
pub created_at: String,
|
||||
pub last_login_at: String,
|
||||
pub updated_at: String,
|
||||
pub last_login_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct UpdateUserDto {
|
||||
// Empty as per API schema
|
||||
}
|
||||
|
||||
pub struct UserRemote {
|
||||
@@ -47,5 +58,25 @@ impl UserRemote {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
// TODO: Add other endpoints as methods
|
||||
pub async fn update_user(
|
||||
&self,
|
||||
user_id: Option<&str>,
|
||||
update: UpdateUserDto,
|
||||
) -> Result<UserProfile, Error> {
|
||||
let url = format!("{}/users/{}", self.base_url, user_id.unwrap_or("me"));
|
||||
let resp = with_auth(self.client.put(url))
|
||||
.await
|
||||
.json(&update)
|
||||
.send()
|
||||
.await?;
|
||||
let user = resp.json().await?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn delete_user(&self, user_id: Option<&str>) -> Result<(), Error> {
|
||||
let url = format!("{}/users/{}", self.base_url, user_id.unwrap_or("me"));
|
||||
let resp = with_auth(self.client.delete(url)).await.send().await?;
|
||||
resp.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use rust_socketio::{ClientBuilder, Payload, RawClient};
|
||||
use serde_json::json;
|
||||
use tauri::async_runtime;
|
||||
use tauri::{async_runtime, Emitter};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
lock_r, lock_w,
|
||||
services::cursor::CursorPosition,
|
||||
{models::app_config::AppConfig, state::FDOLL},
|
||||
get_app_handle, lock_r, lock_w, models::app_config::AppConfig,
|
||||
services::cursor::CursorPosition, state::FDOLL,
|
||||
};
|
||||
|
||||
#[allow(non_camel_case_types)] // pretend to be a const like in js
|
||||
@@ -14,13 +13,54 @@ pub struct WS_EVENT;
|
||||
|
||||
impl WS_EVENT {
|
||||
pub const CURSOR_REPORT_POSITION: &str = "cursor-report-position";
|
||||
pub const FRIEND_REQUEST_RECEIVED: &str = "friend-request-received";
|
||||
pub const FRIEND_REQUEST_ACCEPTED: &str = "friend-request-accepted";
|
||||
pub const FRIEND_REQUEST_DENIED: &str = "friend-request-denied";
|
||||
pub const UNFRIENDED: &str = "unfriended";
|
||||
}
|
||||
|
||||
// Define a callback for handling incoming messages (e.g., 'pong')
|
||||
fn on_pong(payload: Payload, _socket: RawClient) {
|
||||
fn on_friend_request_received(payload: Payload, _socket: RawClient) {
|
||||
match payload {
|
||||
Payload::Text(str) => println!("Received pong: {:?}", str),
|
||||
Payload::Binary(bin) => println!("Received pong (binary): {:?}", bin),
|
||||
Payload::Text(str) => {
|
||||
println!("Received friend request: {:?}", str);
|
||||
get_app_handle()
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_RECEIVED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_friend_request_accepted(payload: Payload, _socket: RawClient) {
|
||||
match payload {
|
||||
Payload::Text(str) => {
|
||||
println!("Received friend request accepted: {:?}", str);
|
||||
get_app_handle()
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_ACCEPTED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_friend_request_denied(payload: Payload, _socket: RawClient) {
|
||||
match payload {
|
||||
Payload::Text(str) => {
|
||||
println!("Received friend request denied: {:?}", str);
|
||||
get_app_handle()
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_DENIED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_unfriended(payload: Payload, _socket: RawClient) {
|
||||
match payload {
|
||||
Payload::Text(str) => {
|
||||
println!("Received unfriended: {:?}", str);
|
||||
get_app_handle().emit(WS_EVENT::UNFRIENDED, str).unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -82,7 +122,16 @@ pub async fn build_ws_client(app_config: &AppConfig) -> rust_socketio::client::C
|
||||
let client = async_runtime::spawn_blocking(move || {
|
||||
ClientBuilder::new(api_base_url)
|
||||
.namespace("/")
|
||||
.on("pong", on_pong)
|
||||
.on(
|
||||
WS_EVENT::FRIEND_REQUEST_RECEIVED,
|
||||
on_friend_request_received,
|
||||
)
|
||||
.on(
|
||||
WS_EVENT::FRIEND_REQUEST_ACCEPTED,
|
||||
on_friend_request_accepted,
|
||||
)
|
||||
.on(WS_EVENT::FRIEND_REQUEST_DENIED, on_friend_request_denied)
|
||||
.on(WS_EVENT::UNFRIENDED, on_unfriended)
|
||||
.auth(json!({ "token": token }))
|
||||
.connect()
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
app_config::{AppConfig, AuthConfig},
|
||||
app_data::AppData,
|
||||
},
|
||||
remotes::user::UserRemote,
|
||||
remotes::{friends::FriendRemote, user::UserRemote},
|
||||
services::auth::{load_auth_pass, AuthPass},
|
||||
};
|
||||
use serde_json::json;
|
||||
@@ -99,13 +99,20 @@ pub fn init_fdoll_state() {
|
||||
/// Populate user data in app state from the server.
|
||||
pub async fn init_app_data() {
|
||||
let user_remote = UserRemote::new();
|
||||
let friend_remote = FriendRemote::new();
|
||||
let user = user_remote
|
||||
.get_user(None)
|
||||
.await
|
||||
.expect("TODO: handle user profile fetch failure");
|
||||
let friends = friend_remote
|
||||
.get_friends()
|
||||
.await
|
||||
.expect("TODO: handle friends fetch failure");
|
||||
|
||||
{
|
||||
let mut guard = lock_w!(FDOLL);
|
||||
guard.app_data.user = Some(user);
|
||||
guard.app_data.friends = Some(friends);
|
||||
get_app_handle()
|
||||
.emit("app-data-refreshed", json!(guard.app_data))
|
||||
.expect("TODO: handle event emit fail");
|
||||
|
||||
13
src/assets/icons/power.svelte
Normal file
13
src/assets/icons/power.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-power-icon lucide-power"
|
||||
><path d="M12 2v10" /><path d="M18.4 6.6a9 9 0 1 1-12.77.04" /></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 320 B |
@@ -2,14 +2,15 @@
|
||||
import Friends from "./tabs/friends.svelte";
|
||||
import Preferences from "./tabs/preferences.svelte";
|
||||
import YourDolls from "./tabs/your-dolls.svelte";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="p-2 h-full absolute inset-0 bg-base-100 border-base-200/50 border border-t-0 rounded-b-xl"
|
||||
>
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<div class="size-full">
|
||||
<div class="tabs tabs-lift h-full">
|
||||
<div class="size-full flex flex-col gap-2">
|
||||
<div class="tabs tabs-lift h-full flex-1">
|
||||
<input
|
||||
type="radio"
|
||||
name="app_menu_tabs"
|
||||
@@ -41,6 +42,17 @@
|
||||
<Preferences />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<div></div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-outline border-neutral-500/50"
|
||||
onclick={async () => {
|
||||
await getCurrentWebviewWindow().close();
|
||||
}}><p class="px-4">Ok</p></button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { appData } from "../../../events/app-data";
|
||||
import Power from "../../../assets/icons/power.svelte";
|
||||
</script>
|
||||
|
||||
<div class="size-full flex flex-col justify-between">
|
||||
@@ -11,11 +12,15 @@
|
||||
<div></div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-error btn-sm btn-soft"
|
||||
class="btn btn-error btn-square btn-soft"
|
||||
onclick={async () => {
|
||||
await invoke("quit_app");
|
||||
}}>Quit Friendolls</button
|
||||
}}
|
||||
>
|
||||
<div class="scale-50">
|
||||
<Power />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user