minor cleanup with traits

This commit is contained in:
Evan Carroll 2026-01-23 18:27:54 -06:00
parent 73f9c95e37
commit 8a37a7b2da
17 changed files with 81 additions and 71 deletions

View file

@ -6,7 +6,7 @@ use chattyness_db::{
models::{CreateServerAvatarRequest, ServerAvatar, ServerAvatarSummary, UpdateServerAvatarRequest},
queries::server_avatars,
};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::Serialize;
use sqlx::PgPool;
use uuid::Uuid;
@ -87,7 +87,7 @@ pub async fn get_avatar(
) -> Result<Json<ServerAvatar>, AppError> {
let avatar = server_avatars::get_server_avatar_by_id(&pool, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
Ok(Json(avatar))
}
@ -106,7 +106,7 @@ pub async fn update_avatar(
// Check avatar exists
let existing = server_avatars::get_server_avatar_by_id(&mut *guard, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
// Update the avatar
let avatar = server_avatars::update_server_avatar(&mut *guard, avatar_id, &req).await?;
@ -127,7 +127,7 @@ pub async fn delete_avatar(
// Get the avatar first to log its name
let avatar = server_avatars::get_server_avatar_by_id(&mut *guard, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
// Delete from database
server_avatars::delete_server_avatar(&mut *guard, avatar_id).await?;

View file

@ -3,7 +3,7 @@
use axum::Json;
use axum::extract::Path;
use chattyness_db::{models::LooseProp, queries::loose_props};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::Deserialize;
use uuid::Uuid;
@ -34,7 +34,7 @@ pub async fn get_loose_prop(
let prop = loose_props::get_loose_prop_by_id(&mut *guard, loose_prop_id)
.await?
.ok_or_else(|| AppError::NotFound("Loose prop not found or has expired".to_string()))?;
.or_not_found("Loose prop (may have expired)")?;
Ok(Json(prop))
}

View file

@ -7,7 +7,7 @@ use chattyness_db::{
models::{CreateServerPropRequest, ServerProp, ServerPropSummary},
queries::props,
};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
@ -218,7 +218,7 @@ pub async fn get_prop(
) -> Result<Json<ServerProp>, AppError> {
let prop = props::get_server_prop_by_id(&pool, prop_id)
.await?
.ok_or_else(|| AppError::NotFound("Prop not found".to_string()))?;
.or_not_found("Prop")?;
Ok(Json(prop))
}
@ -233,7 +233,7 @@ pub async fn delete_prop(
// Get the prop first to get the asset path
let prop = props::get_server_prop_by_id(&mut *guard, prop_id)
.await?
.ok_or_else(|| AppError::NotFound("Prop not found".to_string()))?;
.or_not_found("Prop")?;
// Delete from database
props::delete_server_prop(&mut *guard, prop_id).await?;

View file

@ -11,7 +11,7 @@ use chattyness_db::{
},
queries::{owner as queries, realm_avatars},
};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;
@ -231,7 +231,7 @@ pub async fn get_realm_avatar(
let avatar = realm_avatars::get_realm_avatar_by_id(&pool, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
Ok(Json(avatar))
}
@ -254,7 +254,7 @@ pub async fn update_realm_avatar(
// Check avatar exists
let existing = realm_avatars::get_realm_avatar_by_id(&mut *guard, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
// Update the avatar
let avatar = realm_avatars::update_realm_avatar(&mut *guard, avatar_id, &req).await?;
@ -284,7 +284,7 @@ pub async fn delete_realm_avatar(
// Get the avatar first to log its name
let avatar = realm_avatars::get_realm_avatar_by_id(&mut *guard, avatar_id)
.await?
.ok_or_else(|| AppError::NotFound("Avatar not found".to_string()))?;
.or_not_found("Avatar")?;
// Delete from database
realm_avatars::delete_realm_avatar(&mut *guard, avatar_id).await?;

View file

@ -8,7 +8,7 @@ use chattyness_db::{
models::{CreateSceneRequest, Scene, SceneSummary, UpdateSceneRequest},
queries::{realms, scenes},
};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::path::PathBuf;
@ -199,7 +199,7 @@ pub async fn get_scene(
) -> Result<Json<Scene>, AppError> {
let scene = scenes::get_scene_by_id(&pool, scene_id)
.await?
.ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;
.or_not_found("Scene")?;
Ok(Json(scene))
}
@ -273,7 +273,7 @@ pub async fn update_scene(
// Get the existing scene to get realm_id
let existing_scene = scenes::get_scene_by_id(&mut *guard, scene_id)
.await?
.ok_or_else(|| AppError::NotFound("Scene not found".to_string()))?;
.or_not_found("Scene")?;
// Handle clear background image
if req.clear_background_image {

View file

@ -8,7 +8,7 @@ use chattyness_db::{
models::{CreateSpotRequest, Spot, SpotSummary, UpdateSpotRequest},
queries::spots,
};
use chattyness_error::AppError;
use chattyness_error::{AppError, OptionExt};
use serde::Serialize;
use sqlx::PgPool;
use uuid::Uuid;
@ -31,7 +31,7 @@ pub async fn get_spot(
) -> Result<Json<Spot>, AppError> {
let spot = spots::get_spot_by_id(&pool, spot_id)
.await?
.ok_or_else(|| AppError::NotFound("Spot not found".to_string()))?;
.or_not_found("Spot")?;
Ok(Json(spot))
}
@ -78,7 +78,7 @@ pub async fn update_spot(
if let Some(ref new_slug) = req.slug {
let existing = spots::get_spot_by_id(&mut *guard, spot_id)
.await?
.ok_or_else(|| AppError::NotFound("Spot not found".to_string()))?;
.or_not_found("Spot")?;
if Some(new_slug.clone()) != existing.slug {
let available =

View file

@ -5,6 +5,7 @@ use leptos::prelude::*;
use leptos::task::spawn_local;
use crate::components::{Card, PageHeader};
use crate::utils::name_to_slug;
/// Server avatar new page component.
#[component]
@ -28,14 +29,7 @@ pub fn AvatarsNewPage() -> impl IntoView {
let new_name = event_target_value(&ev);
set_name.set(new_name.clone());
if slug_auto.get() {
let new_slug = new_name
.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string();
set_slug.set(new_slug);
set_slug.set(name_to_slug(&new_name));
}
};

View file

@ -5,6 +5,7 @@ use leptos::prelude::*;
use leptos::task::spawn_local;
use crate::components::{Card, PageHeader};
use crate::utils::name_to_slug;
/// Prop new page component with file upload.
#[component]
@ -32,14 +33,7 @@ pub fn PropsNewPage() -> impl IntoView {
let new_name = event_target_value(&ev);
set_name.set(new_name.clone());
if slug_auto.get() {
let new_slug = new_name
.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string();
set_slug.set(new_slug);
set_slug.set(name_to_slug(&new_name));
}
};

View file

@ -6,6 +6,7 @@ use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use crate::components::{Card, PageHeader};
use crate::utils::name_to_slug;
#[cfg(feature = "hydrate")]
use crate::utils::get_api_base;
@ -36,14 +37,7 @@ pub fn RealmAvatarsNewPage() -> impl IntoView {
let new_name = event_target_value(&ev);
set_name.set(new_name.clone());
if slug_auto.get() {
let new_slug = new_name
.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string();
set_avatar_slug.set(new_slug);
set_avatar_slug.set(name_to_slug(&new_name));
}
};

View file

@ -5,6 +5,7 @@ use leptos::prelude::*;
use leptos::task::spawn_local;
use crate::components::{Card, PageHeader};
use crate::utils::name_to_slug;
/// Realm new page component.
#[component]
@ -40,14 +41,7 @@ pub fn RealmNewPage() -> impl IntoView {
let new_name = event_target_value(&ev);
set_name.set(new_name.clone());
if slug_auto.get() {
let new_slug = new_name
.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string();
set_slug.set(new_slug);
set_slug.set(name_to_slug(&new_name));
}
};

View file

@ -6,6 +6,7 @@ use leptos::task::spawn_local;
use leptos_router::hooks::use_params_map;
use crate::components::{Card, PageHeader};
use crate::utils::name_to_slug;
#[cfg(feature = "hydrate")]
use crate::utils::fetch_image_dimensions_client;
@ -40,14 +41,7 @@ pub fn SceneNewPage() -> impl IntoView {
let new_name = event_target_value(&ev);
set_name.set(new_name.clone());
if slug_auto.get() {
let new_slug = new_name
.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string();
set_slug.set(new_slug);
set_slug.set(name_to_slug(&new_name));
}
};

View file

@ -1,5 +1,24 @@
//! Utility functions for the admin UI.
/// Generate a URL-friendly slug from a name.
///
/// Converts to lowercase, replaces non-alphanumeric chars with hyphens,
/// and trims leading/trailing hyphens.
///
/// # Example
/// ```rust
/// assert_eq!(name_to_slug("My Cool Realm!"), "my-cool-realm-");
/// assert_eq!(name_to_slug("Test 123"), "test-123");
/// ```
pub fn name_to_slug(name: &str) -> String {
name.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect::<String>()
.trim_matches('-')
.to_string()
}
/// Gets the API base path based on the current URL.
///
/// Returns `/api/admin` if the current path starts with `/admin`,