Silence warnings, run cargo fmt

This commit is contained in:
Evan Carroll 2026-01-18 16:27:31 -06:00
parent fe1c1d3655
commit af1c767f5f
77 changed files with 1904 additions and 903 deletions

View file

@ -1,6 +1,6 @@
//! Admin authentication API handlers.
use axum::{extract::State, http::StatusCode, Json};
use axum::{Json, extract::State, http::StatusCode};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use tower_sessions::Session;
@ -221,21 +221,16 @@ pub async fn get_auth_context(
session: Session,
) -> Result<Json<AuthContextResponse>, (StatusCode, Json<ErrorResponse>)> {
// Try to get staff_id from session (server staff)
let staff_id: Option<uuid::Uuid> = session
.get(ADMIN_SESSION_STAFF_ID_KEY)
.await
.ok()
.flatten();
let staff_id: Option<uuid::Uuid> = session.get(ADMIN_SESSION_STAFF_ID_KEY).await.ok().flatten();
if let Some(staff_id) = staff_id {
// Check if this is actually a staff member
let is_staff: Option<bool> = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM server.staff WHERE user_id = $1)",
)
.bind(staff_id)
.fetch_one(&pool)
.await
.ok();
let is_staff: Option<bool> =
sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server.staff WHERE user_id = $1)")
.bind(staff_id)
.fetch_one(&pool)
.await
.ok();
if is_staff == Some(true) {
return Ok(Json(AuthContextResponse {

View file

@ -1,6 +1,6 @@
//! Server config API handlers.
use axum::{extract::State, Json};
use axum::{Json, extract::State};
use chattyness_db::{
models::{ServerConfig, UpdateServerConfigRequest},
queries::owner as queries,
@ -9,9 +9,7 @@ use chattyness_error::AppError;
use sqlx::PgPool;
/// Get server config.
pub async fn get_config(
State(pool): State<PgPool>,
) -> Result<Json<ServerConfig>, AppError> {
pub async fn get_config(State(pool): State<PgPool>) -> Result<Json<ServerConfig>, AppError> {
let config = queries::get_server_config(&pool).await?;
Ok(Json(config))
}

View file

@ -1,6 +1,6 @@
//! Dashboard API handlers.
use axum::{extract::State, Json};
use axum::{Json, extract::State};
use chattyness_error::AppError;
use serde::Serialize;
use sqlx::PgPool;
@ -16,9 +16,7 @@ pub struct DashboardStats {
}
/// Get dashboard stats.
pub async fn get_stats(
State(pool): State<PgPool>,
) -> Result<Json<DashboardStats>, AppError> {
pub async fn get_stats(State(pool): State<PgPool>) -> Result<Json<DashboardStats>, AppError> {
// Total users
let total_users: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM auth.users")
.fetch_one(&pool)

View file

@ -1,14 +1,14 @@
//! Props management API handlers for admin UI.
use axum::extract::{Query, State};
use axum::Json;
use axum::extract::{Query, State};
use axum_extra::extract::Multipart;
use serde::Deserialize;
use chattyness_db::{
models::{CreateServerPropRequest, ServerProp, ServerPropSummary},
queries::props,
};
use chattyness_error::AppError;
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use sqlx::PgPool;
@ -64,9 +64,7 @@ fn validate_file_extension(filename: &str) -> Result<&'static str, AppError> {
match ext.as_str() {
"svg" => Ok("svg"),
"png" => Ok("png"),
_ => Err(AppError::Validation(
"File must be SVG or PNG".to_string(),
)),
_ => Err(AppError::Validation("File must be SVG or PNG".to_string())),
}
}
@ -101,7 +99,9 @@ async fn store_prop_file(bytes: &[u8], extension: &str) -> Result<String, AppErr
// =============================================================================
/// List all server props.
pub async fn list_props(State(pool): State<PgPool>) -> Result<Json<Vec<ServerPropSummary>>, AppError> {
pub async fn list_props(
State(pool): State<PgPool>,
) -> Result<Json<Vec<ServerPropSummary>>, AppError> {
let prop_list = props::list_server_props(&pool).await?;
Ok(Json(prop_list))
}
@ -137,9 +137,10 @@ pub async fn create_prop(
.await
.map_err(|e| AppError::Validation(format!("Failed to read metadata: {}", e)))?;
metadata = Some(serde_json::from_str(&text).map_err(|e| {
AppError::Validation(format!("Invalid metadata JSON: {}", e))
})?);
metadata =
Some(serde_json::from_str(&text).map_err(|e| {
AppError::Validation(format!("Invalid metadata JSON: {}", e))
})?);
}
"file" => {
let filename = field

View file

@ -1,8 +1,8 @@
//! Realm management API handlers.
use axum::{
extract::{Path, Query, State},
Json,
extract::{Path, Query, State},
};
use chattyness_db::{
models::{OwnerCreateRealmRequest, RealmDetail, RealmListItem, UpdateRealmRequest},

View file

@ -1,8 +1,8 @@
//! Admin API routes.
use axum::{
routing::{delete, get, post, put},
Router,
routing::{delete, get, post, put},
};
use super::{auth, config, dashboard, props, realms, scenes, spots, staff, users};
@ -56,10 +56,7 @@ pub fn admin_api_router() -> Router<AdminAppState> {
"/realms/{slug}",
get(realms::get_realm).put(realms::update_realm),
)
.route(
"/realms/{slug}/transfer",
post(realms::transfer_ownership),
)
.route("/realms/{slug}/transfer", post(realms::transfer_ownership))
// API - Scenes
.route(
"/realms/{slug}/scenes",

View file

@ -1,8 +1,8 @@
//! Scene management API handlers for admin UI.
use axum::{
extract::{Path, State},
Json,
extract::{Path, State},
};
use chattyness_db::{
models::{CreateSceneRequest, Scene, SceneSummary, UpdateSceneRequest},
@ -136,10 +136,7 @@ async fn download_and_store_image(
.map_err(|e| AppError::Internal(format!("Failed to write image file: {}", e)))?;
// Return the URL path (relative to server root)
let local_path = format!(
"/static/realm/{}/scene/{}/{}",
realm_id, scene_id, filename
);
let local_path = format!("/static/realm/{}/scene/{}/{}", realm_id, scene_id, filename);
Ok(ImageDownloadResult {
local_path,
@ -237,13 +234,9 @@ pub async fn create_scene(
// Handle background image URL - download and store locally
if let Some(ref url) = req.background_image_url {
if !url.is_empty() {
let result = download_and_store_image(
url,
realm.id,
scene_id,
req.infer_dimensions_from_image,
)
.await?;
let result =
download_and_store_image(url, realm.id, scene_id, req.infer_dimensions_from_image)
.await?;
req.background_image_path = Some(result.local_path);

View file

@ -1,8 +1,8 @@
//! Spot management API handlers for admin UI.
use axum::{
extract::{Path, State},
Json,
extract::{Path, State},
};
use chattyness_db::{
models::{CreateSpotRequest, Spot, SpotSummary, UpdateSpotRequest},
@ -73,7 +73,8 @@ pub async fn update_spot(
.ok_or_else(|| AppError::NotFound("Spot not found".to_string()))?;
if Some(new_slug.clone()) != existing.slug {
let available = spots::is_spot_slug_available(&pool, existing.scene_id, new_slug).await?;
let available =
spots::is_spot_slug_available(&pool, existing.scene_id, new_slug).await?;
if !available {
return Err(AppError::Conflict(format!(
"Spot slug '{}' is already taken in this scene",

View file

@ -1,8 +1,8 @@
//! Staff management API handlers.
use axum::{
extract::{Path, State},
Json,
extract::{Path, State},
};
use chattyness_db::{
models::{CreateStaffRequest, StaffMember},
@ -21,9 +21,7 @@ pub struct CreateStaffResponse {
}
/// List all staff members.
pub async fn list_staff(
State(pool): State<PgPool>,
) -> Result<Json<Vec<StaffMember>>, AppError> {
pub async fn list_staff(State(pool): State<PgPool>) -> Result<Json<Vec<StaffMember>>, AppError> {
let staff = queries::get_all_staff(&pool).await?;
Ok(Json(staff))
}
@ -44,11 +42,7 @@ pub async fn create_staff(
}))
} else if let Some(ref new_user) = req.new_user {
// Create new user and promote to staff
let (user_id, temporary_password) = queries::create_user(
&pool,
new_user,
)
.await?;
let (user_id, temporary_password) = queries::create_user(&pool, new_user).await?;
let staff = queries::create_staff(&pool, user_id, req.role, None).await?;
Ok(Json(CreateStaffResponse {
staff,

View file

@ -1,8 +1,8 @@
//! User management API handlers.
use axum::{
extract::{Path, Query, State},
Json,
extract::{Path, Query, State},
};
use chattyness_db::{
models::{
@ -153,9 +153,7 @@ pub async fn remove_from_realm(
}
/// List all realms (for dropdown).
pub async fn list_realms(
State(pool): State<PgPool>,
) -> Result<Json<Vec<RealmSummary>>, AppError> {
pub async fn list_realms(State(pool): State<PgPool>) -> Result<Json<Vec<RealmSummary>>, AppError> {
let realms = queries::list_all_realms(&pool).await?;
Ok(Json(realms))
}

View file

@ -1,7 +1,7 @@
//! Admin Leptos application root and router.
use leptos::prelude::*;
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
use leptos_router::components::Router;
use crate::routes::AdminRoutes;

View file

@ -12,7 +12,7 @@ use axum::{
#[cfg(feature = "ssr")]
use sqlx::PgPool;
#[cfg(feature = "ssr")]
use tower_sessions::{cookie::time::Duration, cookie::SameSite, Expiry, SessionManagerLayer};
use tower_sessions::{Expiry, SessionManagerLayer, cookie::SameSite, cookie::time::Duration};
#[cfg(feature = "ssr")]
use tower_sessions_sqlx_store::PostgresStore;
#[cfg(feature = "ssr")]

View file

@ -110,8 +110,7 @@ pub fn use_auth_context() -> LocalResource<Option<AuthContextResponse>> {
#[component]
pub fn AuthenticatedLayout(
current_page: &'static str,
#[prop(default = "/admin")]
base_path: &'static str,
#[prop(default = "/admin")] base_path: &'static str,
children: ChildrenFn,
) -> impl IntoView {
let auth_context = use_auth_context();
@ -165,10 +164,8 @@ pub fn AuthenticatedLayout(
fn Sidebar(
current_page: &'static str,
base_path: &'static str,
#[prop(default = false)]
is_server_staff: bool,
#[prop(default = vec![])]
managed_realms: Vec<(String, String)>,
#[prop(default = false)] is_server_staff: bool,
#[prop(default = vec![])] managed_realms: Vec<(String, String)>,
) -> impl IntoView {
// Build hrefs with base path
let dashboard_href = base_path.to_string();
@ -319,13 +316,22 @@ fn NavItem(
label: &'static str,
#[prop(default = false)] active: bool,
/// Whether this is a sub-item (indented)
#[prop(default = false)] sub: bool,
#[prop(default = false)]
sub: bool,
) -> impl IntoView {
let link_class = match (active, sub) {
(true, false) => "block w-full px-6 py-2 bg-violet-600 text-white transition-all duration-150",
(false, false) => "block w-full px-6 py-2 text-gray-400 hover:bg-slate-700 hover:text-white transition-all duration-150",
(true, true) => "block w-full pl-10 pr-6 py-2 text-sm bg-violet-600 text-white transition-all duration-150",
(false, true) => "block w-full pl-10 pr-6 py-2 text-sm text-gray-400 hover:bg-slate-700 hover:text-white transition-all duration-150",
(true, false) => {
"block w-full px-6 py-2 bg-violet-600 text-white transition-all duration-150"
}
(false, false) => {
"block w-full px-6 py-2 text-gray-400 hover:bg-slate-700 hover:text-white transition-all duration-150"
}
(true, true) => {
"block w-full pl-10 pr-6 py-2 text-sm bg-violet-600 text-white transition-all duration-150"
}
(false, true) => {
"block w-full pl-10 pr-6 py-2 text-sm text-gray-400 hover:bg-slate-700 hover:text-white transition-all duration-150"
}
};
view! {

View file

@ -154,11 +154,7 @@ where
};
let response = if let Some(body) = body {
request
.json(body)
.map_err(|e| e.to_string())?
.send()
.await
request.json(body).map_err(|e| e.to_string())?.send().await
} else {
request.send().await
}
@ -200,11 +196,7 @@ pub async fn api_request_simple(
};
let response = if let Some(body) = body {
request
.json(body)
.map_err(|e| e.to_string())?
.send()
.await
request.json(body).map_err(|e| e.to_string())?.send().await
} else {
request.send().await
}

View file

@ -31,7 +31,7 @@ pub mod pages;
pub mod routes;
pub mod utils;
pub use app::{admin_shell, AdminApp};
pub use app::{AdminApp, admin_shell};
pub use routes::AdminRoutes;
// Re-export commonly used items for convenience
@ -40,7 +40,7 @@ pub use components::{
MessageAlert, MessageAlertRw, NsfwBadge, PageHeader, Pagination, PrivacyBadge, RoleBadge,
SearchForm, StatusBadge, SubmitButton, TempPasswordDisplay,
};
pub use hooks::{use_fetch, use_fetch_if, use_message, use_pagination, PaginationState};
pub use hooks::{PaginationState, use_fetch, use_fetch_if, use_message, use_pagination};
pub use models::*;
pub use utils::{build_bounds_wkt, build_paginated_url, get_api_base, parse_bounds_wkt};

View file

@ -1,9 +1,9 @@
//! Realm detail/edit page component.
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[cfg(feature = "hydrate")]
use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use crate::components::{
Card, DetailGrid, DetailItem, MessageAlert, NsfwBadge, PageHeader, PrivacyBadge,
@ -73,8 +73,12 @@ fn RealmDetailView(
let (max_users, set_max_users) = signal(realm.max_users);
let (is_nsfw, set_is_nsfw) = signal(realm.is_nsfw);
let (allow_guest_access, set_allow_guest_access) = signal(realm.allow_guest_access);
let (theme_color, set_theme_color) =
signal(realm.theme_color.clone().unwrap_or_else(|| "#7c3aed".to_string()));
let (theme_color, set_theme_color) = signal(
realm
.theme_color
.clone()
.unwrap_or_else(|| "#7c3aed".to_string()),
);
let on_submit = move |ev: leptos::ev::SubmitEvent| {
ev.prevent_default();

View file

@ -80,8 +80,14 @@ pub fn RealmNewPage() -> impl IntoView {
}
data["owner_id"] = serde_json::json!(owner_id.get());
} else {
if new_username.get().is_empty() || new_email.get().is_empty() || new_display_name.get().is_empty() {
set_message.set(Some(("Please fill in all new owner fields".to_string(), false)));
if new_username.get().is_empty()
|| new_email.get().is_empty()
|| new_display_name.get().is_empty()
{
set_message.set(Some((
"Please fill in all new owner fields".to_string(),
false,
)));
set_pending.set(false);
return;
}
@ -111,7 +117,8 @@ pub fn RealmNewPage() -> impl IntoView {
if let Ok(result) = resp.json::<CreateResponse>().await {
set_created_slug.set(Some(result.slug));
set_temp_password.set(result.owner_temporary_password);
set_message.set(Some(("Realm created successfully!".to_string(), true)));
set_message
.set(Some(("Realm created successfully!".to_string(), true)));
}
}
Ok(resp) => {

View file

@ -1,9 +1,9 @@
//! Scene detail/edit page component.
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[cfg(feature = "hydrate")]
use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use uuid::Uuid;
use crate::components::{Card, DetailGrid, DetailItem, PageHeader};
@ -74,7 +74,9 @@ pub fn SceneDetailPage() -> impl IntoView {
#[cfg(feature = "hydrate")]
{
use gloo_net::http::Request;
let resp = Request::get(&format!("/api/admin/scenes/{}", id)).send().await;
let resp = Request::get(&format!("/api/admin/scenes/{}", id))
.send()
.await;
match resp {
Ok(r) if r.ok() => r.json::<SceneDetail>().await.ok(),
_ => None,
@ -153,7 +155,10 @@ fn SceneDetailView(
let (name, set_name) = signal(scene.name.clone());
let (description, set_description) = signal(scene.description.clone().unwrap_or_default());
let (background_color, set_background_color) = signal(
scene.background_color.clone().unwrap_or_else(|| "#1a1a2e".to_string()),
scene
.background_color
.clone()
.unwrap_or_else(|| "#1a1a2e".to_string()),
);
let (background_image_url, set_background_image_url) = signal(String::new());
let (clear_background_image, set_clear_background_image) = signal(false);
@ -257,7 +262,6 @@ fn SceneDetailView(
}
};
view! {
<Card>
<div class="realm-header">

View file

@ -1,9 +1,9 @@
//! Create new scene page component.
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[cfg(feature = "hydrate")]
use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use crate::components::{Card, PageHeader};
#[cfg(feature = "hydrate")]
@ -106,11 +106,7 @@ pub fn SceneNewPage() -> impl IntoView {
spawn_local(async move {
let url = format!("/api/admin/realms/{}/scenes", realm_slug_val);
let response = Request::post(&url)
.json(&data)
.unwrap()
.send()
.await;
let response = Request::post(&url).json(&data).unwrap().send().await;
set_pending.set(false);
@ -124,7 +120,8 @@ pub fn SceneNewPage() -> impl IntoView {
}
if let Ok(result) = resp.json::<CreateResponse>().await {
set_created_id.set(Some(result.id));
set_message.set(Some(("Scene created successfully!".to_string(), true)));
set_message
.set(Some(("Scene created successfully!".to_string(), true)));
}
}
Ok(resp) => {

View file

@ -205,10 +205,7 @@ fn AddStaffButton(message: RwSignal<Option<(String, bool)>>) -> impl IntoView {
#[component]
#[allow(unused_variables)]
fn RemoveStaffButton(
user_id: String,
message: RwSignal<Option<(String, bool)>>,
) -> impl IntoView {
fn RemoveStaffButton(user_id: String, message: RwSignal<Option<(String, bool)>>) -> impl IntoView {
let (pending, set_pending) = signal(false);
#[cfg(feature = "hydrate")]
let user_id_for_click = user_id.clone();

View file

@ -1,11 +1,13 @@
//! User detail page component.
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[cfg(feature = "hydrate")]
use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use crate::components::{Card, DetailGrid, DetailItem, MessageAlert, PageHeader, StatusBadge, TempPasswordDisplay};
use crate::components::{
Card, DetailGrid, DetailItem, MessageAlert, PageHeader, StatusBadge, TempPasswordDisplay,
};
use crate::hooks::use_fetch_if;
use crate::models::UserDetail;
#[cfg(feature = "hydrate")]
@ -114,9 +116,10 @@ fn UserDetailView(
let user_id = user_id_for_reset.clone();
spawn_local(async move {
let response = Request::post(&format!("/api/admin/users/{}/reset-password", user_id))
.send()
.await;
let response =
Request::post(&format!("/api/admin/users/{}/reset-password", user_id))
.send()
.await;
set_pending_reset.set(false);
@ -128,7 +131,8 @@ fn UserDetailView(
}
if let Ok(result) = resp.json::<ResetResponse>().await {
set_new_password.set(Some(result.temporary_password));
set_message.set(Some(("Password reset successfully!".to_string(), true)));
set_message
.set(Some(("Password reset successfully!".to_string(), true)));
}
}
_ => {

View file

@ -9,8 +9,8 @@
use leptos::prelude::*;
use leptos_router::{
components::{Route, Routes},
ParamSegment, StaticSegment,
components::{Route, Routes},
};
use crate::components::{AuthenticatedLayout, LoginLayout};

View file

@ -128,14 +128,13 @@ pub fn fetch_image_dimensions_client<F, E>(
on_success: F,
on_error: E,
set_loading: leptos::prelude::WriteSignal<bool>,
)
where
) where
F: Fn(u32, u32) + 'static,
E: Fn(String) + Clone + 'static,
{
use leptos::prelude::Set;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
let on_error_for_onerror = on_error.clone();