fetch user profile (wip)
This commit is contained in:
14
README.md
14
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
|
||||
```
|
||||
|
||||
@@ -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<tauri::AppHandle<tauri::Wry>> = 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<AppData, String> {
|
||||
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())
|
||||
|
||||
@@ -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<String>,
|
||||
pub auth: AuthConfig,
|
||||
|
||||
10
src-tauri/src/models/app_data.rs
Normal file
10
src-tauri/src/models/app_data.rs
Normal file
@@ -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<UserProfile>,
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod app_config;
|
||||
pub mod app_data;
|
||||
|
||||
1
src-tauri/src/remotes/mod.rs
Normal file
1
src-tauri/src/remotes/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod user;
|
||||
51
src-tauri/src/remotes/user.rs
Normal file
51
src-tauri/src/remotes/user.rs
Normal file
@@ -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<UserProfile, Error> {
|
||||
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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Clients>,
|
||||
pub auth_pass: Option<AuthPass>,
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CursorPositions>({
|
||||
raw: { x: 0, y: 0 },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { initCursorTracking, stopCursorTracking } from '../events/cursor';
|
||||
import { browser } from "$app/environment";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { initCursorTracking, stopCursorTracking } from "../events/cursor";
|
||||
|
||||
let { children } = $props();
|
||||
if (browser) {
|
||||
@@ -17,7 +17,6 @@
|
||||
stopCursorTracking();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="size-full bg-transparent">
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
cursorPositionOnScreen,
|
||||
} from "../../events/cursor";
|
||||
|
||||
import { cursorPositionOnScreen } from "../../events/cursor";
|
||||
</script>
|
||||
|
||||
<div class="p-4">
|
||||
@@ -17,12 +14,22 @@
|
||||
<h3 class="font-semibold mb-2">Cursor Position (Multi-Window Test)</h3>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-mono text-sm">
|
||||
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y})
|
||||
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw
|
||||
.y})
|
||||
</span>
|
||||
<span class="font-mono text-sm">
|
||||
Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen.mapped.y})
|
||||
Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen
|
||||
.mapped.y})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
onclick={() => {
|
||||
invoke("get_app_data").then((data) => {
|
||||
console.log("data", data);
|
||||
});
|
||||
}}>Fetch app data</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
cursorPositionOnScreen,
|
||||
} from "../../events/cursor";
|
||||
import { cursorPositionOnScreen } from "../../events/cursor";
|
||||
</script>
|
||||
|
||||
<div class="w-svw h-svh p-4 relative">
|
||||
@@ -13,7 +11,8 @@
|
||||
<p class="text-sm opacity-50">Scene Screen</p>
|
||||
<div class="mt-4 flex flex-col gap-1">
|
||||
<span class="font-mono text-sm">
|
||||
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y})
|
||||
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw
|
||||
.y})
|
||||
</span>
|
||||
<span class="font-mono text-sm">
|
||||
Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen
|
||||
|
||||
4
src/types/bindings/AppData.ts
Normal file
4
src/types/bindings/AppData.ts
Normal file
@@ -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, };
|
||||
3
src/types/bindings/CursorPosition.ts
Normal file
3
src/types/bindings/CursorPosition.ts
Normal file
@@ -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, };
|
||||
4
src/types/bindings/CursorPositions.ts
Normal file
4
src/types/bindings/CursorPositions.ts
Normal file
@@ -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, };
|
||||
3
src/types/bindings/UserProfile.ts
Normal file
3
src/types/bindings/UserProfile.ts
Normal file
@@ -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, };
|
||||
Reference in New Issue
Block a user