//! 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, ) -> Result, 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, Json(req): Json, ) -> Result, 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, Json(req): Json, ) -> Result, 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)) }