added server and realm tabs to inventory screen

This commit is contained in:
Evan Carroll 2026-01-16 16:47:30 -06:00
parent ee425e224e
commit acab2f017d
12 changed files with 647 additions and 151 deletions

View file

@ -640,6 +640,23 @@ pub struct InventoryResponse {
pub items: Vec<InventoryItem>,
}
/// A public prop from server or realm library.
/// Used for the public inventory tabs (Server/Realm).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct PublicProp {
pub id: Uuid,
pub name: String,
pub asset_path: String,
pub description: Option<String>,
}
/// Response for public props list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicPropsResponse {
pub props: Vec<PublicProp>,
}
/// A prop dropped in a channel, available for pickup.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
@ -680,6 +697,7 @@ pub struct ServerProp {
pub is_transferable: bool,
pub is_portable: bool,
pub is_droppable: bool,
pub is_public: bool,
pub is_active: bool,
pub available_from: Option<DateTime<Utc>>,
pub available_until: Option<DateTime<Utc>>,
@ -720,6 +738,12 @@ pub struct CreateServerPropRequest {
/// Grid position (0-8): top row 0,1,2 / middle 3,4,5 / bottom 6,7,8
#[serde(default)]
pub default_position: Option<i16>,
/// Whether prop is droppable (can be dropped in a channel).
#[serde(default)]
pub droppable: Option<bool>,
/// Whether prop appears in the public Server inventory tab.
#[serde(default)]
pub public: Option<bool>,
}
#[cfg(feature = "ssr")]

View file

@ -3,7 +3,7 @@
use sqlx::PgExecutor;
use uuid::Uuid;
use crate::models::InventoryItem;
use crate::models::{InventoryItem, PublicProp};
use chattyness_error::AppError;
/// List all inventory items for a user.
@ -91,3 +91,67 @@ pub async fn drop_inventory_item<'e>(
Ok(())
}
/// List all public server props.
///
/// Returns props that are:
/// - Active (`is_active = true`)
/// - Public (`is_public = true`)
/// - Currently available (within availability window if set)
pub async fn list_public_server_props<'e>(
executor: impl PgExecutor<'e>,
) -> Result<Vec<PublicProp>, AppError> {
let props = sqlx::query_as::<_, PublicProp>(
r#"
SELECT
id,
name,
asset_path,
description
FROM server.props
WHERE is_active = true
AND is_public = true
AND (available_from IS NULL OR available_from <= now())
AND (available_until IS NULL OR available_until > now())
ORDER BY name ASC
"#,
)
.fetch_all(executor)
.await?;
Ok(props)
}
/// List all public realm props for a specific realm.
///
/// Returns props that are:
/// - In the specified realm
/// - Active (`is_active = true`)
/// - Public (`is_public = true`)
/// - Currently available (within availability window if set)
pub async fn list_public_realm_props<'e>(
executor: impl PgExecutor<'e>,
realm_id: Uuid,
) -> Result<Vec<PublicProp>, AppError> {
let props = sqlx::query_as::<_, PublicProp>(
r#"
SELECT
id,
name,
asset_path,
description
FROM realm.props
WHERE realm_id = $1
AND is_active = true
AND is_public = true
AND (available_from IS NULL OR available_from <= now())
AND (available_until IS NULL OR available_until > now())
ORDER BY name ASC
"#,
)
.bind(realm_id)
.fetch_all(executor)
.await?;
Ok(props)
}

View file

@ -52,6 +52,7 @@ pub async fn get_server_prop_by_id<'e>(
is_transferable,
is_portable,
is_droppable,
is_public,
is_active,
available_from,
available_until,
@ -114,17 +115,22 @@ pub async fn create_server_prop<'e>(
(None, None, None)
};
let is_droppable = req.droppable.unwrap_or(true);
let is_public = req.public.unwrap_or(false);
let prop = sqlx::query_as::<_, ServerProp>(
r#"
INSERT INTO server.props (
name, slug, description, tags, asset_path,
default_layer, default_emotion, default_position,
is_droppable, is_public,
created_by
)
VALUES (
$1, $2, $3, $4, $5,
$6::server.avatar_layer, $7::server.emotion_state, $8,
$9
$9, $10,
$11
)
RETURNING
id,
@ -141,6 +147,7 @@ pub async fn create_server_prop<'e>(
is_transferable,
is_portable,
is_droppable,
is_public,
is_active,
available_from,
available_until,
@ -157,6 +164,8 @@ pub async fn create_server_prop<'e>(
.bind(&default_layer)
.bind(&default_emotion)
.bind(default_position)
.bind(is_droppable)
.bind(is_public)
.bind(created_by)
.fetch_one(executor)
.await?;
@ -198,17 +207,22 @@ pub async fn upsert_server_prop<'e>(
(None, None, None)
};
let is_droppable = req.droppable.unwrap_or(true);
let is_public = req.public.unwrap_or(false);
let prop = sqlx::query_as::<_, ServerProp>(
r#"
INSERT INTO server.props (
name, slug, description, tags, asset_path,
default_layer, default_emotion, default_position,
is_droppable, is_public,
created_by
)
VALUES (
$1, $2, $3, $4, $5,
$6::server.avatar_layer, $7::server.emotion_state, $8,
$9
$9, $10,
$11
)
ON CONFLICT (slug) DO UPDATE SET
name = EXCLUDED.name,
@ -218,6 +232,8 @@ pub async fn upsert_server_prop<'e>(
default_layer = EXCLUDED.default_layer,
default_emotion = EXCLUDED.default_emotion,
default_position = EXCLUDED.default_position,
is_droppable = EXCLUDED.is_droppable,
is_public = EXCLUDED.is_public,
updated_at = now()
RETURNING
id,
@ -234,6 +250,7 @@ pub async fn upsert_server_prop<'e>(
is_transferable,
is_portable,
is_droppable,
is_public,
is_active,
available_from,
available_until,
@ -250,6 +267,8 @@ pub async fn upsert_server_prop<'e>(
.bind(&default_layer)
.bind(&default_emotion)
.bind(default_position)
.bind(is_droppable)
.bind(is_public)
.bind(created_by)
.fetch_one(executor)
.await?;