Add account connections, guild wallets and admin fixes [skip ci]
This commit is contained in:
1 parent
7076e847ea
commit
004862ea88
38 files changed
+1696
-458
No files matched your search
+206
-100
@@ -7,9 +7,8 @@ use crate::state::{build, AppState};
|
||||
use crate::updater;
|
||||
use scopenet_core::ping::ServerStatus;
|
||||
use scopenet_shared::{
|
||||
Achievement, BaltopEntry, DirectMessage, EconomyTransaction, FriendInfo, GameInvite, Guild,
|
||||
GuildClaim, GuildMember, GuildPost, LauncherManifest, MemberProfile, PlayerProfile,
|
||||
ServerEconomyBalance, SkinProfile, UserLevelInfo, UserPost, UserProfileView, UserQuest,
|
||||
Achievement, BaltopEntry, DirectMessage, EconomyTransaction, FriendInfo, GameInvite, Guild, GuildClaim, GuildMember, GuildPost,
|
||||
LauncherManifest, MemberProfile, PlayerProfile, ServerEconomyBalance, SkinProfile, UserLevelInfo, UserPost, UserProfileView, UserQuest,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
@@ -56,7 +55,10 @@ pub struct Bootstrap {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RunningGameInfo { run_id: String, instance_id: String }
|
||||
struct RunningGameInfo {
|
||||
run_id: String,
|
||||
instance_id: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn bootstrap(state: State<'_, AppState>) -> Bootstrap {
|
||||
@@ -77,7 +79,13 @@ pub fn bootstrap(state: State<'_, AppState>) -> Bootstrap {
|
||||
accounts: accounts.accounts,
|
||||
active_account: accounts.active,
|
||||
manifest: state.manifest.read().unwrap().clone(),
|
||||
game_running: state.game.lock().unwrap().iter().map(|g| RunningGameInfo { run_id: g.run_id.clone(), instance_id: g.instance_id.clone() }).collect(),
|
||||
game_running: state
|
||||
.game
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|g| RunningGameInfo { run_id: g.run_id.clone(), instance_id: g.instance_id.clone() })
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +142,9 @@ pub fn save_instance_options(state: State<'_, AppState>, instance_id: String) ->
|
||||
return Err("close this instance before saving its game settings".into());
|
||||
}
|
||||
let options = scopenet_core::options::read_vanilla_preferences(&state.layout.instance_dir(&instance_id)).map_err(aerr)?;
|
||||
if options.is_empty() { return Err("no vanilla options.txt settings found yet".into()); }
|
||||
if options.is_empty() {
|
||||
return Err("no vanilla options.txt settings found yet".into());
|
||||
}
|
||||
state.settings.write().unwrap().instance_game_options.insert(instance_id, options.clone());
|
||||
state.save_settings().map_err(aerr)?;
|
||||
Ok(options)
|
||||
@@ -147,6 +157,80 @@ pub async fn login_panel(state: State<'_, AppState>, username: String, password:
|
||||
accounts::login_panel(&state, &username, &password).await.map_err(aerr)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discord_sign_in_start(state: State<'_, AppState>) -> Res<serde_json::Value> {
|
||||
let panel = state.panel_url().ok_or("no panel configured")?;
|
||||
let response = state.http.get(format!("{panel}/api/v1/auth/discord/start")).send().await.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
response.json().await.map_err(err)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discord_sign_in_poll(state: State<'_, AppState>, oauth_state: String) -> Res<serde_json::Value> {
|
||||
let panel = state.panel_url().ok_or("no panel configured")?;
|
||||
let response =
|
||||
state.http.get(format!("{panel}/api/v1/auth/discord/poll")).query(&[("state", oauth_state)]).send().await.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
let result: serde_json::Value = response.json().await.map_err(err)?;
|
||||
if result.get("token").is_some() {
|
||||
let auth = serde_json::from_value(result.clone()).map_err(err)?;
|
||||
let account = accounts::save_panel_account(&state, &panel, auth).map_err(aerr)?;
|
||||
return Ok(serde_json::json!({"account":account}));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn request_password_reset(state: State<'_, AppState>, email: String) -> Res<String> {
|
||||
let panel = state.panel_url().ok_or("no panel configured")?;
|
||||
let response = state
|
||||
.http
|
||||
.post(format!("{panel}/api/v1/auth/forgot-password"))
|
||||
.json(&serde_json::json!({"email":email}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
let body: serde_json::Value = response.json().await.map_err(err)?;
|
||||
Ok(body["message"].as_str().unwrap_or("Check your email for a reset link.").to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discord_connection(state: State<'_, AppState>) -> Res<serde_json::Value> {
|
||||
let req = account_api(&state, reqwest::Method::GET, "/account/connections/discord").await?;
|
||||
let response = req.send().await.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
response.json().await.map_err(err)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discord_link_start(state: State<'_, AppState>) -> Res<serde_json::Value> {
|
||||
let req = account_api(&state, reqwest::Method::GET, "/auth/discord/start?kind=link").await?;
|
||||
let response = req.send().await.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
response.json().await.map_err(err)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discord_unlink(state: State<'_, AppState>) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::DELETE, "/account/connections/discord").await?;
|
||||
let response = req.send().await.map_err(err)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(accounts::panel_error(response).await.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RegisterResult {
|
||||
account: Option<Account>,
|
||||
@@ -242,10 +326,7 @@ pub async fn set_cape(state: State<'_, AppState>, cape_id: Option<i64>) -> Res<P
|
||||
|
||||
fn load_skin_profiles(data_dir: &std::path::Path) -> Vec<SkinProfile> {
|
||||
let path = data_dir.join("skin_profiles.json");
|
||||
std::fs::read(&path)
|
||||
.ok()
|
||||
.and_then(|b| serde_json::from_slice(&b).ok())
|
||||
.unwrap_or_default()
|
||||
std::fs::read(&path).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn write_skin_profiles(data_dir: &std::path::Path, profiles: &[SkinProfile]) -> anyhow::Result<()> {
|
||||
@@ -283,10 +364,7 @@ pub async fn save_skin_profile(state: State<'_, AppState>, mut profile: SkinProf
|
||||
}
|
||||
}
|
||||
if profile.created_at.is_empty() {
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs().to_string())
|
||||
.unwrap_or_default();
|
||||
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs().to_string()).unwrap_or_default();
|
||||
profile.created_at = ts;
|
||||
}
|
||||
let mut profiles = load_skin_profiles(&state.data_dir);
|
||||
@@ -391,7 +469,9 @@ pub fn cancel_launch(app: AppHandle, state: State<'_, AppState>, instance_id: St
|
||||
#[tauri::command]
|
||||
pub fn kill_game(state: State<'_, AppState>, run_id: String) {
|
||||
if let Some(g) = state.game.lock().unwrap().iter_mut().find(|g| g.run_id == run_id) {
|
||||
if let Some(kill) = g.kill.take() { kill.send(()).ok(); }
|
||||
if let Some(kill) = g.kill.take() {
|
||||
kill.send(()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,11 +592,7 @@ pub async fn get_player_stats(state: State<'_, AppState>) -> Res<serde_json::Val
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_leaderboard(
|
||||
state: State<'_, AppState>,
|
||||
server_id: Option<i64>,
|
||||
sort: Option<String>,
|
||||
) -> Res<serde_json::Value> {
|
||||
pub async fn get_leaderboard(state: State<'_, AppState>, server_id: Option<i64>, sort: Option<String>) -> Res<serde_json::Value> {
|
||||
let panel = state.panel_url().ok_or("no panel configured")?;
|
||||
let sort_query = sort.map(|s| format!("?sort={s}")).unwrap_or_default();
|
||||
let url = if let Some(id) = server_id {
|
||||
@@ -539,9 +615,12 @@ pub async fn get_public_servers(state: State<'_, AppState>) -> Res<serde_json::V
|
||||
return Err("unable to load servers".into());
|
||||
}
|
||||
let data: serde_json::Value = resp.json().await.map_err(err)?;
|
||||
let urls = data["servers"].as_array().into_iter().flatten().filter_map(|s| {
|
||||
Some((s["id"].as_i64()?, s["map_url"].as_str()?.to_owned()))
|
||||
}).collect();
|
||||
let urls = data["servers"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|s| Some((s["id"].as_i64()?, s["map_url"].as_str()?.to_owned())))
|
||||
.collect();
|
||||
*state.map_urls.lock().unwrap() = urls;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -557,24 +636,29 @@ pub struct MapResource {
|
||||
/// cross-origin Vantage live server, so the launcher uses its HTTP client.
|
||||
/// Only paths under a map URL supplied by the panel can be fetched.
|
||||
#[tauri::command]
|
||||
pub async fn fetch_vantage_resource(
|
||||
state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>,
|
||||
) -> Res<MapResource> {
|
||||
pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option<String>) -> Res<MapResource> {
|
||||
use base64::Engine;
|
||||
let configured = state.map_urls.lock().unwrap().get(&server_id).cloned().ok_or("map not configured for this server")?;
|
||||
let base = reqwest::Url::parse(&configured).map_err(err)?;
|
||||
let target = reqwest::Url::parse(&url).map_err(err)?;
|
||||
let root = base.join(".").map_err(err)?;
|
||||
if !matches!(target.scheme(), "http" | "https") || target.origin() != base.origin()
|
||||
|| !target.path().starts_with(root.path()) || target.username() != ""
|
||||
|| target.password().is_some() || target.fragment().is_some() {
|
||||
if !matches!(target.scheme(), "http" | "https")
|
||||
|| target.origin() != base.origin()
|
||||
|| !target.path().starts_with(root.path())
|
||||
|| target.username() != ""
|
||||
|| target.password().is_some()
|
||||
|| target.fragment().is_some()
|
||||
{
|
||||
return Err("map asset is outside the configured Vantage world".into());
|
||||
}
|
||||
if target.as_str().len() > 2048 { return Err("map asset URL is too long".into()); }
|
||||
if target.as_str().len() > 2048 {
|
||||
return Err("map asset URL is too long".into());
|
||||
}
|
||||
let client = reqwest::Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build().map_err(err)?;
|
||||
.build()
|
||||
.map_err(err)?;
|
||||
let mut request = client.get(target);
|
||||
if let Some(etag) = etag.filter(|e| e.len() < 256 && !e.contains('\n') && !e.contains('\r')) {
|
||||
request = request.header(reqwest::header::IF_NONE_MATCH, etag);
|
||||
@@ -582,13 +666,19 @@ pub async fn fetch_vantage_resource(
|
||||
let response = request.send().await.map_err(err)?;
|
||||
let status = response.status().as_u16();
|
||||
let etag = response.headers().get(reqwest::header::ETAG).and_then(|v| v.to_str().ok()).map(str::to_owned);
|
||||
if status == 304 { return Ok(MapResource { status, data: None, etag }); }
|
||||
if !response.status().is_success() { return Ok(MapResource { status, data: None, etag }); }
|
||||
if status == 304 {
|
||||
return Ok(MapResource { status, data: None, etag });
|
||||
}
|
||||
if !response.status().is_success() {
|
||||
return Ok(MapResource { status, data: None, etag });
|
||||
}
|
||||
if response.content_length().is_some_and(|n| n > 64 * 1024 * 1024) {
|
||||
return Err("Vantage asset exceeds 64 MiB".into());
|
||||
}
|
||||
let bytes = response.bytes().await.map_err(err)?;
|
||||
if bytes.len() > 64 * 1024 * 1024 { return Err("Vantage asset exceeds 64 MiB".into()); }
|
||||
if bytes.len() > 64 * 1024 * 1024 {
|
||||
return Err("Vantage asset exceeds 64 MiB".into());
|
||||
}
|
||||
Ok(MapResource { status, data: Some(base64::engine::general_purpose::STANDARD.encode(bytes)), etag })
|
||||
}
|
||||
|
||||
@@ -678,14 +768,18 @@ pub async fn create_guild(
|
||||
banner_url: Option<String>,
|
||||
) -> Res<Guild> {
|
||||
let req = account_api(&state, reqwest::Method::POST, "/guilds").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"name": name,
|
||||
"tag": tag,
|
||||
"description": description,
|
||||
"icon_url": icon_url,
|
||||
"banner_url": banner_url,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"name": name,
|
||||
"tag": tag,
|
||||
"description": description,
|
||||
"icon_url": icon_url,
|
||||
"banner_url": banner_url,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to create guild").to_string());
|
||||
@@ -723,13 +817,17 @@ pub async fn claim_guild_chunk(
|
||||
chunk_z: i32,
|
||||
) -> Res<GuildClaim> {
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/claim")).await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"server_id": server_id,
|
||||
"dimension": dimension,
|
||||
"chunk_x": chunk_x,
|
||||
"chunk_z": chunk_z,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"instance_id": instance_id,
|
||||
"server_id": server_id,
|
||||
"dimension": dimension,
|
||||
"chunk_x": chunk_x,
|
||||
"chunk_z": chunk_z,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to claim chunk").to_string());
|
||||
@@ -755,17 +853,16 @@ pub async fn get_guild_posts(state: State<'_, AppState>, guild_id: String) -> Re
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_guild_post(
|
||||
state: State<'_, AppState>,
|
||||
guild_id: String,
|
||||
title: String,
|
||||
content: String,
|
||||
) -> Res<GuildPost> {
|
||||
pub async fn create_guild_post(state: State<'_, AppState>, guild_id: String, title: String, content: String) -> Res<GuildPost> {
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/posts")).await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"title": title,
|
||||
"content": content,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"title": title,
|
||||
"content": content,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to post announcement").to_string());
|
||||
@@ -783,6 +880,24 @@ pub async fn get_guild_members(state: State<'_, AppState>, guild_id: String) ->
|
||||
resp.json().await.map_err(err)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_guild_wallet(state: State<'_, AppState>, guild_id: String, server_id: i64) -> Res<serde_json::Value> {
|
||||
let req = account_api(&state, reqwest::Method::GET, &format!("/guilds/{guild_id}/wallet?server_id={server_id}")).await?;
|
||||
req.send().await.map_err(err)?.error_for_status().map_err(err)?.json().await.map_err(err)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn transfer_guild_wallet(state: State<'_, AppState>, guild_id: String, server_id: i64, amount: f64, withdraw: bool) -> Res<serde_json::Value> {
|
||||
let action = if withdraw { "withdraw" } else { "deposit" };
|
||||
let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/wallet/{action}")).await?;
|
||||
let resp = req.json(&serde_json::json!({"server_id":server_id,"amount":amount})).send().await.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("Guild wallet transfer failed").to_string());
|
||||
}
|
||||
resp.json().await.map_err(err)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Social: Friends, DMs, Invites, Profiles & Feed
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -798,11 +913,7 @@ pub async fn get_friends(state: State<'_, AppState>) -> Res<Vec<FriendInfo>> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_friend_request(
|
||||
state: State<'_, AppState>,
|
||||
friend_username: Option<String>,
|
||||
username: Option<String>,
|
||||
) -> Res<()> {
|
||||
pub async fn send_friend_request(state: State<'_, AppState>, friend_username: Option<String>, username: Option<String>) -> Res<()> {
|
||||
let target = friend_username.or(username).ok_or("username is required")?;
|
||||
let req = account_api(&state, reqwest::Method::POST, "/friends/request").await?;
|
||||
let resp = req.json(&serde_json::json!({ "username": target })).send().await.map_err(err)?;
|
||||
@@ -831,11 +942,7 @@ pub async fn respond_friend_request(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_friend(
|
||||
state: State<'_, AppState>,
|
||||
friend_uuid: Option<String>,
|
||||
target_uuid: Option<String>,
|
||||
) -> Res<()> {
|
||||
pub async fn remove_friend(state: State<'_, AppState>, friend_uuid: Option<String>, target_uuid: Option<String>) -> Res<()> {
|
||||
let target = friend_uuid.or(target_uuid).ok_or("target_uuid is required")?;
|
||||
let req = account_api(&state, reqwest::Method::DELETE, &format!("/friends/{target}")).await?;
|
||||
let resp = req.send().await.map_err(err)?;
|
||||
@@ -887,11 +994,7 @@ pub async fn get_transactions(state: State<'_, AppState>) -> Res<Vec<EconomyTran
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_direct_messages(
|
||||
state: State<'_, AppState>,
|
||||
friend_uuid: String,
|
||||
before_id: Option<i64>,
|
||||
) -> Res<Vec<DirectMessage>> {
|
||||
pub async fn get_direct_messages(state: State<'_, AppState>, friend_uuid: String, before_id: Option<i64>) -> Res<Vec<DirectMessage>> {
|
||||
// FIX #13: Support optional before_id for loading older messages beyond the first 100.
|
||||
let path = if let Some(before) = before_id {
|
||||
format!("/messages/{friend_uuid}?before_id={before}")
|
||||
@@ -928,18 +1031,17 @@ pub async fn get_game_invites(state: State<'_, AppState>) -> Res<Vec<GameInvite>
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_game_invite(
|
||||
state: State<'_, AppState>,
|
||||
recipient_uuid: String,
|
||||
instance_id: String,
|
||||
server_id: Option<i64>,
|
||||
) -> Res<()> {
|
||||
pub async fn send_game_invite(state: State<'_, AppState>, recipient_uuid: String, instance_id: String, server_id: Option<i64>) -> Res<()> {
|
||||
let req = account_api(&state, reqwest::Method::POST, "/invites").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"recipient_uuid": recipient_uuid,
|
||||
"instance_id": instance_id,
|
||||
"server_id": server_id,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"recipient_uuid": recipient_uuid,
|
||||
"instance_id": instance_id,
|
||||
"server_id": server_id,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to send invite").to_string());
|
||||
@@ -977,12 +1079,16 @@ pub async fn update_my_profile(
|
||||
featured_achievement_id: Option<String>,
|
||||
) -> Res<UserProfileView> {
|
||||
let req = account_api(&state, reqwest::Method::PUT, "/profiles/me").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"bio": bio,
|
||||
"banner_url": banner_url,
|
||||
"custom_badge": custom_badge,
|
||||
"featured_achievement_id": featured_achievement_id,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"bio": bio,
|
||||
"banner_url": banner_url,
|
||||
"custom_badge": custom_badge,
|
||||
"featured_achievement_id": featured_achievement_id,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to update profile").to_string());
|
||||
@@ -1003,16 +1109,16 @@ pub async fn get_user_posts(state: State<'_, AppState>, user_uuid: Option<String
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_user_post(
|
||||
state: State<'_, AppState>,
|
||||
content: String,
|
||||
image_url: Option<String>,
|
||||
) -> Res<UserPost> {
|
||||
pub async fn create_user_post(state: State<'_, AppState>, content: String, image_url: Option<String>) -> Res<UserPost> {
|
||||
let req = account_api(&state, reqwest::Method::POST, "/profiles/me/posts").await?;
|
||||
let resp = req.json(&serde_json::json!({
|
||||
"content": content,
|
||||
"image_url": image_url,
|
||||
})).send().await.map_err(err)?;
|
||||
let resp = req
|
||||
.json(&serde_json::json!({
|
||||
"content": content,
|
||||
"image_url": image_url,
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(err)?;
|
||||
if !resp.status().is_success() {
|
||||
let body: serde_json::Value = resp.json().await.unwrap_or_default();
|
||||
return Err(body["error"].as_str().unwrap_or("unable to publish post").to_string());
|
||||
|
||||
Reference in new issue
Block a user