feat: complete in-game commands, GUIs, admin panel fixes, and launcher enhancements

This commit is contained in:
scoped committed 2026-09-29 12:14:16 -04:00
1 parent a9ff92e1d1
commit 752f0cc43d
34 files changed
+4170 -181

No files matched your search

+131 -10
View File
@@ -103,16 +103,56 @@ pub async fn get_user_levels_by_uuid(state: &AppState, uuid: &str) -> AppResult<
})
.collect();
// Query rewards
let rewards_rows: Vec<(i64, i64, String, String, String)> = sqlx::query_as(
"SELECT id, level_req, reward_type, reward_name, reward_data
FROM level_rewards
ORDER BY level_req ASC",
)
.fetch_all(&state.db)
.await
.unwrap_or_default();
let rewards = rewards_rows
.into_iter()
.map(|(id, req, rtype, rname, rdata)| {
let data: Value = serde_json::from_str(&rdata).unwrap_or_default();
let desc = data.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string();
let icon = data.get("icon").and_then(|v| v.as_str()).map(|s| s.to_string());
scopenet_shared::LevelReward {
id,
level: req,
reward_type: rtype,
reward_value: rname,
description: desc,
icon,
server_id: None,
}
})
.collect();
let rank_opt: Option<i64> = sqlx::query_scalar(
"SELECT COUNT(*) + 1 FROM user_levels WHERE global_xp > ?",
)
.bind(global_xp)
.fetch_optional(&state.db)
.await
.unwrap_or(Some(1));
Ok(UserLevelInfo {
uuid: uuid.to_string(),
global_xp,
current_xp: global_xp,
global_level,
level: global_level,
current_level_xp,
next_level_xp,
progress_pct,
title,
badges,
server_levels,
rank: rank_opt.map(|r| r as usize),
rewards,
})
}
@@ -168,13 +208,32 @@ pub async fn list_rewards(
.into_iter()
.map(|(id, ltype, sid, req, rtype, name, data, created)| {
let parsed_data: Value = serde_json::from_str(&data).unwrap_or_default();
let desc = parsed_data.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let icon = parsed_data.get("icon")
.and_then(|v| v.as_str())
.unwrap_or(match rtype.as_str() {
"title" => "👑",
"profile_badge" | "badge" => "⭐",
"cosmetic" => "✨",
"item" => "🗡️",
_ => "🎁",
})
.to_string();
serde_json::json!({
"id": id,
"level": req,
"level_req": req,
"level_type": ltype,
"server_id": sid,
"level_req": req,
"reward_type": rtype,
"reward_value": name,
"reward_name": name,
"description": if desc.is_empty() { format!("Granted at Level {req}") } else { desc },
"icon": icon,
"reward_data": parsed_data,
"created_at": created
})
@@ -185,12 +244,17 @@ pub async fn list_rewards(
}
#[derive(Deserialize)]
pub struct CreateRewardPayload {
pub level_type: String,
pub struct RewardPayload {
pub id: Option<i64>,
pub level_type: Option<String>,
pub server_id: Option<i64>,
pub level_req: i64,
#[serde(alias = "level")]
pub level_req: Option<i64>,
pub reward_type: String,
pub reward_name: String,
#[serde(alias = "reward_value")]
pub reward_name: Option<String>,
pub description: Option<String>,
pub icon: Option<String>,
pub reward_data: Option<Value>,
}
@@ -198,21 +262,36 @@ pub struct CreateRewardPayload {
pub async fn admin_create_reward(
_admin: AdminUser,
State(state): State<AppState>,
Json(payload): Json<CreateRewardPayload>,
Json(payload): Json<RewardPayload>,
) -> AppResult<Json<Value>> {
let now = chrono::Utc::now().to_rfc3339();
let data_str = serde_json::to_string(&payload.reward_data.unwrap_or_default()).unwrap_or_default();
let level_req = payload.level_req.unwrap_or(2);
let reward_name = payload.reward_name.unwrap_or_else(|| "Reward".into());
let level_type = payload.level_type.unwrap_or_else(|| "global".into());
let mut data_obj = match payload.reward_data {
Some(Value::Object(map)) => map,
_ => serde_json::Map::new(),
};
if let Some(desc) = payload.description {
data_obj.insert("description".into(), Value::String(desc));
}
if let Some(ico) = payload.icon {
data_obj.insert("icon".into(), Value::String(ico));
}
data_obj.insert("title".into(), Value::String(reward_name.clone()));
let data_str = serde_json::to_string(&data_obj).unwrap_or_default();
let id: i64 = sqlx::query_scalar(
"INSERT INTO level_rewards (level_type, server_id, level_req, reward_type, reward_name, reward_data, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
RETURNING id",
)
.bind(payload.level_type)
.bind(level_type)
.bind(payload.server_id)
.bind(payload.level_req)
.bind(level_req)
.bind(payload.reward_type)
.bind(payload.reward_name)
.bind(reward_name)
.bind(data_str)
.bind(&now)
.fetch_one(&state.db)
@@ -221,6 +300,48 @@ pub async fn admin_create_reward(
Ok(Json(serde_json::json!({ "id": id, "ok": true })))
}
/// Admin update reward.
pub async fn admin_update_reward(
_admin: AdminUser,
Path(id): Path<i64>,
State(state): State<AppState>,
Json(payload): Json<RewardPayload>,
) -> AppResult<Json<Value>> {
let level_req = payload.level_req.unwrap_or(2);
let reward_name = payload.reward_name.unwrap_or_else(|| "Reward".into());
let mut data_obj = match payload.reward_data {
Some(Value::Object(map)) => map,
_ => serde_json::Map::new(),
};
if let Some(desc) = payload.description {
data_obj.insert("description".into(), Value::String(desc));
}
if let Some(ico) = payload.icon {
data_obj.insert("icon".into(), Value::String(ico));
}
data_obj.insert("title".into(), Value::String(reward_name.clone()));
let data_str = serde_json::to_string(&data_obj).unwrap_or_default();
sqlx::query(
"UPDATE level_rewards SET
level_req = ?,
reward_type = ?,
reward_name = ?,
reward_data = ?
WHERE id = ?",
)
.bind(level_req)
.bind(payload.reward_type)
.bind(reward_name)
.bind(data_str)
.bind(id)
.execute(&state.db)
.await?;
Ok(Json(serde_json::json!({ "id": id, "ok": true })))
}
/// Admin delete reward.
pub async fn admin_delete_reward(
_admin: AdminUser,