From f522148489d3df00a867bb5f9aa20c8a6a09beb6 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Fri, 5 Dec 2025 21:52:46 +0800 Subject: [PATCH] fetch user profile (wip) --- README.md | 14 +++++--- src-tauri/src/lib.rs | 14 ++++++-- src-tauri/src/models/app_config.rs | 7 ++-- src-tauri/src/models/app_data.rs | 10 ++++++ src-tauri/src/models/mod.rs | 1 + src-tauri/src/remotes/mod.rs | 1 + src-tauri/src/remotes/user.rs | 51 +++++++++++++++++++++++++++ src-tauri/src/services/cursor.rs | 7 ++-- src-tauri/src/state.rs | 41 +++++++++++++++++++-- src/events/cursor.ts | 6 +--- src/routes/+layout.svelte | 35 +++++++++--------- src/routes/+page.svelte | 2 +- src/routes/preferences/+page.svelte | 19 ++++++---- src/routes/scene/+page.svelte | 7 ++-- src/types/bindings/AppData.ts | 4 +++ src/types/bindings/CursorPosition.ts | 3 ++ src/types/bindings/CursorPositions.ts | 4 +++ src/types/bindings/UserProfile.ts | 3 ++ 18 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 src-tauri/src/models/app_data.rs create mode 100644 src-tauri/src/remotes/mod.rs create mode 100644 src-tauri/src/remotes/user.rs create mode 100644 src/types/bindings/AppData.ts create mode 100644 src/types/bindings/CursorPosition.ts create mode 100644 src/types/bindings/CursorPositions.ts create mode 100644 src/types/bindings/UserProfile.ts diff --git a/README.md b/README.md index 858d179..9c7c732 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ -# Tauri + SvelteKit + TypeScript +# Friendolls (Desktop) -This template should help get you started developing with Tauri, SvelteKit and TypeScript in Vite. +Run the following command in project root on first run & after changes to models on Rust side to generate TypeScript type bindings from Rust models -## Recommended IDE Setup +```sh +# unix +TS_RS_EXPORT_DIR="../src/types/bindings" cargo test export_bindings --manifest-path=./src-tauri/Cargo.toml +``` -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). +```sh +# powershell +$Env:TS_RS_EXPORT_DIR = "../src/types/bindings"; cargo test export_bindings --manifest-path=./src-tauri/Cargo.toml +``` diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7d78a83..454f127 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -use crate::services::cursor::start_cursor_tracking; +use crate::{models::app_data::AppData, services::cursor::start_cursor_tracking, state::FDOLL}; use tauri::async_runtime; use tracing_subscriber; @@ -6,6 +6,7 @@ static APP_HANDLE: std::sync::OnceLock> = std::sync mod app; mod models; +mod remotes; mod services; mod state; mod utilities; @@ -44,13 +45,22 @@ fn register_app_events(event: tauri::RunEvent) { } } +#[tauri::command] +fn get_app_data() -> Result { + let guard = lock_r!(FDOLL); + return Ok(guard.app_data.clone()); +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_positioner::init()) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![start_cursor_tracking]) + .invoke_handler(tauri::generate_handler![ + start_cursor_tracking, + get_app_data + ]) .setup(|app| { APP_HANDLE .set(app.handle().to_owned()) diff --git a/src-tauri/src/models/app_config.rs b/src-tauri/src/models/app_config.rs index a1a8751..6cf4bef 100644 --- a/src-tauri/src/models/app_config.rs +++ b/src-tauri/src/models/app_config.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; -use ts_rs::TS; -#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] -#[ts(export)] +#[derive(Default, Serialize, Deserialize, Clone, Debug)] pub struct AuthConfig { pub audience: String, pub auth_url: String, @@ -10,8 +8,7 @@ pub struct AuthConfig { pub redirect_host: String, } -#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] -#[ts(export)] +#[derive(Default, Serialize, Deserialize, Clone, Debug)] pub struct AppConfig { pub api_base_url: Option, pub auth: AuthConfig, diff --git a/src-tauri/src/models/app_data.rs b/src-tauri/src/models/app_data.rs new file mode 100644 index 0000000..25d6e24 --- /dev/null +++ b/src-tauri/src/models/app_data.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::remotes::user::UserProfile; + +#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] +#[ts(export)] +pub struct AppData { + pub user: Option, +} diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index 62a6f82..7b37fd7 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -1 +1,2 @@ pub mod app_config; +pub mod app_data; diff --git a/src-tauri/src/remotes/mod.rs b/src-tauri/src/remotes/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src-tauri/src/remotes/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src-tauri/src/remotes/user.rs b/src-tauri/src/remotes/user.rs new file mode 100644 index 0000000..404d668 --- /dev/null +++ b/src-tauri/src/remotes/user.rs @@ -0,0 +1,51 @@ +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 UserProfile { + pub id: String, + pub name: String, + pub email: String, + pub username: String, + pub created_at: String, + pub last_login_at: String, +} + +pub struct UserRemote { + pub base_url: String, + pub client: Client, +} + +impl UserRemote { + 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_user(&self, user_id: Option<&str>) -> Result { + let url = format!("{}/users/{}", self.base_url, user_id.unwrap_or("me")); + let resp = with_auth(self.client.get(url)).await.send().await?; + let user = resp.json().await?; + Ok(user) + } + + // TODO: Add other endpoints as methods +} diff --git a/src-tauri/src/services/cursor.rs b/src-tauri/src/services/cursor.rs index 27417eb..cd04a02 100644 --- a/src-tauri/src/services/cursor.rs +++ b/src-tauri/src/services/cursor.rs @@ -6,18 +6,21 @@ use std::sync::Arc; use std::time::Duration; use tauri::Emitter; use tracing::{error, info, warn}; +use ts_rs::TS; use crate::get_app_handle; -#[derive(Clone, Serialize)] +#[derive(Clone, Serialize, TS)] #[serde(rename_all = "camelCase")] +#[ts(export)] pub struct CursorPosition { pub x: i32, pub y: i32, } -#[derive(Clone, Serialize)] +#[derive(Clone, Serialize, TS)] #[serde(rename_all = "camelCase")] +#[ts(export)] pub struct CursorPositions { pub raw: CursorPosition, pub mapped: CursorPosition, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 60385a5..d97c2eb 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -1,14 +1,20 @@ // in app-core/src/state.rs use crate::{ lock_w, - models::app_config::{AppConfig, AuthConfig}, + models::{ + app_config::{AppConfig, AuthConfig}, + app_data::AppData, + }, + remotes::user::UserRemote, services::auth::{load_auth_pass, AuthPass}, + APP_HANDLE, }; +use serde_json::json; use std::{ env, sync::{Arc, LazyLock, RwLock}, }; -use tauri::async_runtime; +use tauri::{async_runtime, Emitter}; use tracing::{info, warn}; #[derive(Default, Clone)] @@ -29,6 +35,9 @@ pub struct AppState { pub clients: Option, pub auth_pass: Option, pub oauth_flow: OAuthFlowTracker, + + // exposed to the frontend + pub app_data: AppData, } // Global application state @@ -81,8 +90,34 @@ pub fn init_fdoll_state() { async_runtime::spawn(async move { crate::services::ws::init_ws_client().await; }); + + // TODO: seems like even under `has_auth` token may not be present when init app data + async_runtime::spawn(async move { + info!("Initializing user data"); + init_app_data().await; + }); } - info!("Initialized FDOLL state (WebSocket client initializing asynchronously)"); + info!("Initialized FDOLL state (WebSocket client & user data initializing asynchronously)"); + } +} + +/// To be called in init state or need to refresh data. +/// Populate user data in app state from the server. +pub async fn init_app_data() { + let user_remote = UserRemote::new(); + let user = user_remote + .get_user(None) + .await + .expect("TODO: handle user profile fetch failure"); + { + let mut guard = lock_w!(FDOLL); + guard.app_data.user = Some(user); + APP_HANDLE + .get() + // TODO: magic constants + .expect("App handle not initialized") + .emit("app-data-refreshed", json!(guard.app_data)) + .expect("TODO: handle event emit fail"); } } diff --git a/src/events/cursor.ts b/src/events/cursor.ts index bce4904..5ce267f 100644 --- a/src/events/cursor.ts +++ b/src/events/cursor.ts @@ -1,11 +1,7 @@ import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { writable } from "svelte/store"; - -export type CursorPositions = { - raw: { x: number; y: number }; - mapped: { x: number; y: number }; -}; +import type { CursorPositions } from "../types/bindings/CursorPositions"; export let cursorPositionOnScreen = writable({ raw: { x: 0, y: 0 }, diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 581aa8a..44918e1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,25 +1,24 @@
- {@render children?.()} + {@render children?.()}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index c9648a1..79c5dd4 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,3 +1,3 @@
- +
diff --git a/src/routes/preferences/+page.svelte b/src/routes/preferences/+page.svelte index 7b1418e..32c45ee 100644 --- a/src/routes/preferences/+page.svelte +++ b/src/routes/preferences/+page.svelte @@ -1,9 +1,6 @@
@@ -17,12 +14,22 @@

Cursor Position (Multi-Window Test)

- Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y}) + Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw + .y}) - Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen.mapped.y}) + Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen + .mapped.y})
+ diff --git a/src/routes/scene/+page.svelte b/src/routes/scene/+page.svelte index a50fac2..2f7070b 100644 --- a/src/routes/scene/+page.svelte +++ b/src/routes/scene/+page.svelte @@ -1,7 +1,5 @@
@@ -13,7 +11,8 @@

Scene Screen

- Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y}) + Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw + .y}) Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen diff --git a/src/types/bindings/AppData.ts b/src/types/bindings/AppData.ts new file mode 100644 index 0000000..81fc52c --- /dev/null +++ b/src/types/bindings/AppData.ts @@ -0,0 +1,4 @@ +// 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"; + +export type AppData = { user: UserProfile | null, }; diff --git a/src/types/bindings/CursorPosition.ts b/src/types/bindings/CursorPosition.ts new file mode 100644 index 0000000..ab9bae8 --- /dev/null +++ b/src/types/bindings/CursorPosition.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CursorPosition = { x: number, y: number, }; diff --git a/src/types/bindings/CursorPositions.ts b/src/types/bindings/CursorPositions.ts new file mode 100644 index 0000000..f2a3aff --- /dev/null +++ b/src/types/bindings/CursorPositions.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CursorPosition } from "./CursorPosition"; + +export type CursorPositions = { raw: CursorPosition, mapped: CursorPosition, }; diff --git a/src/types/bindings/UserProfile.ts b/src/types/bindings/UserProfile.ts new file mode 100644 index 0000000..ee6cca6 --- /dev/null +++ b/src/types/bindings/UserProfile.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UserProfile = { id: string, name: string, email: string, username: string, createdAt: string, lastLoginAt: string, };