129 lines
3.6 KiB
Rust
129 lines
3.6 KiB
Rust
//! Avatar API handlers for user UI.
|
|
//!
|
|
//! Handles avatar data retrieval and slot updates.
|
|
//! Note: Emotion switching is handled via WebSocket.
|
|
|
|
use axum::Json;
|
|
use axum::extract::Path;
|
|
|
|
use chattyness_db::{
|
|
models::{AssignSlotRequest, AvatarWithPaths, ClearSlotRequest},
|
|
queries::{avatars, realms},
|
|
};
|
|
use chattyness_error::AppError;
|
|
|
|
use crate::auth::{AuthUser, RlsConn};
|
|
|
|
/// Get full avatar with all paths resolved.
|
|
///
|
|
/// GET /api/realms/{slug}/avatar
|
|
///
|
|
/// Returns the complete avatar data with all inventory UUIDs resolved to asset paths.
|
|
/// This enables client-side emotion availability computation and rendering without
|
|
/// additional server queries.
|
|
pub async fn get_avatar(
|
|
rls_conn: RlsConn,
|
|
AuthUser(user): AuthUser,
|
|
Path(slug): Path<String>,
|
|
) -> Result<Json<AvatarWithPaths>, AppError> {
|
|
let mut conn = rls_conn.acquire().await;
|
|
|
|
// Get realm
|
|
let realm = realms::get_realm_by_slug(&mut *conn, &slug)
|
|
.await?
|
|
.ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;
|
|
|
|
// Get full avatar with paths
|
|
let avatar = avatars::get_avatar_with_paths_conn(&mut *conn, user.id, realm.id)
|
|
.await?
|
|
.unwrap_or_default();
|
|
|
|
Ok(Json(avatar))
|
|
}
|
|
|
|
/// Assign an inventory item to an avatar slot.
|
|
///
|
|
/// PUT /api/realms/{slug}/avatar/slot
|
|
///
|
|
/// Assigns the specified inventory item to the given layer and position
|
|
/// in the user's active avatar.
|
|
pub async fn assign_slot(
|
|
rls_conn: RlsConn,
|
|
AuthUser(user): AuthUser,
|
|
Path(slug): Path<String>,
|
|
Json(req): Json<AssignSlotRequest>,
|
|
) -> Result<Json<AvatarWithPaths>, AppError> {
|
|
// Guests cannot customize their avatar
|
|
if user.is_guest() {
|
|
return Err(AppError::Forbidden(
|
|
"Avatar customization is disabled for guests, please register first.".to_string(),
|
|
));
|
|
}
|
|
|
|
req.validate()?;
|
|
|
|
let mut conn = rls_conn.acquire().await;
|
|
|
|
// Get realm
|
|
let realm = realms::get_realm_by_slug(&mut *conn, &slug)
|
|
.await?
|
|
.ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;
|
|
|
|
// Update the slot
|
|
let column_name = req.column_name();
|
|
avatars::update_avatar_slot(
|
|
&mut *conn,
|
|
user.id,
|
|
realm.id,
|
|
&column_name,
|
|
Some(req.inventory_item_id),
|
|
)
|
|
.await?;
|
|
|
|
// Return updated avatar
|
|
let avatar = avatars::get_avatar_with_paths_conn(&mut *conn, user.id, realm.id)
|
|
.await?
|
|
.unwrap_or_default();
|
|
|
|
Ok(Json(avatar))
|
|
}
|
|
|
|
/// Clear an avatar slot.
|
|
///
|
|
/// DELETE /api/realms/{slug}/avatar/slot
|
|
///
|
|
/// Removes any inventory item from the given layer and position
|
|
/// in the user's active avatar.
|
|
pub async fn clear_slot(
|
|
rls_conn: RlsConn,
|
|
AuthUser(user): AuthUser,
|
|
Path(slug): Path<String>,
|
|
Json(req): Json<ClearSlotRequest>,
|
|
) -> Result<Json<AvatarWithPaths>, AppError> {
|
|
// Guests cannot customize their avatar
|
|
if user.is_guest() {
|
|
return Err(AppError::Forbidden(
|
|
"Avatar customization is disabled for guests, please register first.".to_string(),
|
|
));
|
|
}
|
|
|
|
req.validate()?;
|
|
|
|
let mut conn = rls_conn.acquire().await;
|
|
|
|
// Get realm
|
|
let realm = realms::get_realm_by_slug(&mut *conn, &slug)
|
|
.await?
|
|
.ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", slug)))?;
|
|
|
|
// Clear the slot
|
|
let column_name = req.column_name();
|
|
avatars::update_avatar_slot(&mut *conn, user.id, realm.id, &column_name, None).await?;
|
|
|
|
// Return updated avatar
|
|
let avatar = avatars::get_avatar_with_paths_conn(&mut *conn, user.id, realm.id)
|
|
.await?
|
|
.unwrap_or_default();
|
|
|
|
Ok(Json(avatar))
|
|
}
|