add the ability to make props non-droppable
This commit is contained in:
parent
ea3b444d71
commit
845d64c981
5 changed files with 82 additions and 14 deletions
|
|
@ -614,6 +614,7 @@ pub struct InventoryItem {
|
||||||
pub layer: Option<AvatarLayer>,
|
pub layer: Option<AvatarLayer>,
|
||||||
pub is_transferable: bool,
|
pub is_transferable: bool,
|
||||||
pub is_portable: bool,
|
pub is_portable: bool,
|
||||||
|
pub is_droppable: bool,
|
||||||
pub origin: PropOrigin,
|
pub origin: PropOrigin,
|
||||||
pub acquired_at: DateTime<Utc>,
|
pub acquired_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
@ -663,6 +664,7 @@ pub struct ServerProp {
|
||||||
pub is_unique: bool,
|
pub is_unique: bool,
|
||||||
pub is_transferable: bool,
|
pub is_transferable: bool,
|
||||||
pub is_portable: bool,
|
pub is_portable: bool,
|
||||||
|
pub is_droppable: bool,
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
pub available_from: Option<DateTime<Utc>>,
|
pub available_from: Option<DateTime<Utc>>,
|
||||||
pub available_until: Option<DateTime<Utc>>,
|
pub available_until: Option<DateTime<Utc>>,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ pub async fn list_user_inventory<'e>(
|
||||||
layer,
|
layer,
|
||||||
is_transferable,
|
is_transferable,
|
||||||
is_portable,
|
is_portable,
|
||||||
|
is_droppable,
|
||||||
origin,
|
origin,
|
||||||
acquired_at
|
acquired_at
|
||||||
FROM props.inventory
|
FROM props.inventory
|
||||||
|
|
@ -35,16 +36,30 @@ pub async fn list_user_inventory<'e>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop a prop (remove from inventory).
|
/// Drop a prop (remove from inventory).
|
||||||
|
/// Returns an error if the prop is non-droppable (essential prop).
|
||||||
pub async fn drop_inventory_item<'e>(
|
pub async fn drop_inventory_item<'e>(
|
||||||
executor: impl PgExecutor<'e>,
|
executor: impl PgExecutor<'e>,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
item_id: Uuid,
|
item_id: Uuid,
|
||||||
) -> Result<(), AppError> {
|
) -> Result<(), AppError> {
|
||||||
let result = sqlx::query(
|
// Use a CTE to check existence/droppability and delete in a single query
|
||||||
|
// Returns: (existed, was_droppable, was_deleted)
|
||||||
|
let result: Option<(bool, bool, bool)> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
DELETE FROM props.inventory
|
WITH item_info AS (
|
||||||
WHERE id = $1 AND user_id = $2
|
SELECT id, is_droppable
|
||||||
RETURNING id
|
FROM props.inventory
|
||||||
|
WHERE id = $1 AND user_id = $2
|
||||||
|
),
|
||||||
|
deleted AS (
|
||||||
|
DELETE FROM props.inventory
|
||||||
|
WHERE id = $1 AND user_id = $2 AND is_droppable = true
|
||||||
|
RETURNING id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
EXISTS(SELECT 1 FROM item_info) AS existed,
|
||||||
|
COALESCE((SELECT is_droppable FROM item_info), false) AS was_droppable,
|
||||||
|
EXISTS(SELECT 1 FROM deleted) AS was_deleted
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
|
|
@ -52,10 +67,26 @@ pub async fn drop_inventory_item<'e>(
|
||||||
.fetch_optional(executor)
|
.fetch_optional(executor)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if result.is_none() {
|
match result {
|
||||||
return Err(AppError::NotFound(
|
Some((false, _, _)) | None => {
|
||||||
"Inventory item not found or not owned by user".to_string(),
|
return Err(AppError::NotFound(
|
||||||
));
|
"Inventory item not found or not owned by user".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some((true, false, _)) => {
|
||||||
|
return Err(AppError::Forbidden(
|
||||||
|
"This prop cannot be dropped - it is an essential prop".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some((true, true, true)) => {
|
||||||
|
// Successfully deleted
|
||||||
|
}
|
||||||
|
Some((true, true, false)) => {
|
||||||
|
// Should not happen - item existed, was droppable, but wasn't deleted
|
||||||
|
return Err(AppError::Internal(
|
||||||
|
"Unexpected error dropping inventory item".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,40 @@ pub async fn list_channel_loose_props<'e>(
|
||||||
///
|
///
|
||||||
/// Deletes from inventory and inserts into loose_props with 30-minute expiry.
|
/// Deletes from inventory and inserts into loose_props with 30-minute expiry.
|
||||||
/// Returns the created loose prop.
|
/// Returns the created loose prop.
|
||||||
pub async fn drop_prop_to_canvas<'e>(
|
/// Returns an error if the prop is non-droppable (essential prop).
|
||||||
executor: impl PgExecutor<'e>,
|
pub async fn drop_prop_to_canvas(
|
||||||
|
pool: &sqlx::PgPool,
|
||||||
inventory_item_id: Uuid,
|
inventory_item_id: Uuid,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
channel_id: Uuid,
|
channel_id: Uuid,
|
||||||
position_x: f64,
|
position_x: f64,
|
||||||
position_y: f64,
|
position_y: f64,
|
||||||
) -> Result<LooseProp, AppError> {
|
) -> Result<LooseProp, AppError> {
|
||||||
|
// First check if the item exists and is droppable
|
||||||
|
let item_check: Option<(bool,)> = sqlx::query_as(
|
||||||
|
r#"SELECT is_droppable FROM props.inventory WHERE id = $1 AND user_id = $2"#,
|
||||||
|
)
|
||||||
|
.bind(inventory_item_id)
|
||||||
|
.bind(user_id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match item_check {
|
||||||
|
None => {
|
||||||
|
return Err(AppError::NotFound(
|
||||||
|
"Inventory item not found or not owned by user".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some((false,)) => {
|
||||||
|
return Err(AppError::Forbidden(
|
||||||
|
"This prop cannot be dropped - it is an essential prop".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some((true,)) => {
|
||||||
|
// Item is droppable, proceed with the drop operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use a CTE to delete from inventory and insert to loose_props in one query
|
// Use a CTE to delete from inventory and insert to loose_props in one query
|
||||||
let prop = sqlx::query_as::<_, LooseProp>(
|
let prop = sqlx::query_as::<_, LooseProp>(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -111,10 +137,10 @@ pub async fn drop_prop_to_canvas<'e>(
|
||||||
.bind(channel_id)
|
.bind(channel_id)
|
||||||
.bind(position_x as f32)
|
.bind(position_x as f32)
|
||||||
.bind(position_y as f32)
|
.bind(position_y as f32)
|
||||||
.fetch_optional(executor)
|
.fetch_optional(pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
AppError::NotFound("Inventory item not found or not owned by user".to_string())
|
AppError::Internal("Unexpected error dropping prop to canvas".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(prop)
|
Ok(prop)
|
||||||
|
|
@ -144,6 +170,7 @@ pub async fn pick_up_loose_prop<'e>(
|
||||||
COALESCE(sp.default_layer, rp.default_layer) as layer,
|
COALESCE(sp.default_layer, rp.default_layer) as layer,
|
||||||
COALESCE(sp.is_transferable, rp.is_transferable) as is_transferable,
|
COALESCE(sp.is_transferable, rp.is_transferable) as is_transferable,
|
||||||
COALESCE(sp.is_portable, true) as is_portable,
|
COALESCE(sp.is_portable, true) as is_portable,
|
||||||
|
COALESCE(sp.is_droppable, rp.is_droppable, true) as is_droppable,
|
||||||
dp.server_prop_id,
|
dp.server_prop_id,
|
||||||
dp.realm_prop_id
|
dp.realm_prop_id
|
||||||
FROM deleted_prop dp
|
FROM deleted_prop dp
|
||||||
|
|
@ -161,6 +188,7 @@ pub async fn pick_up_loose_prop<'e>(
|
||||||
origin,
|
origin,
|
||||||
is_transferable,
|
is_transferable,
|
||||||
is_portable,
|
is_portable,
|
||||||
|
is_droppable,
|
||||||
provenance,
|
provenance,
|
||||||
acquired_at
|
acquired_at
|
||||||
)
|
)
|
||||||
|
|
@ -174,10 +202,11 @@ pub async fn pick_up_loose_prop<'e>(
|
||||||
'server_library'::props.prop_origin,
|
'server_library'::props.prop_origin,
|
||||||
COALESCE(si.is_transferable, true),
|
COALESCE(si.is_transferable, true),
|
||||||
COALESCE(si.is_portable, true),
|
COALESCE(si.is_portable, true),
|
||||||
|
COALESCE(si.is_droppable, true),
|
||||||
'[]'::jsonb,
|
'[]'::jsonb,
|
||||||
now()
|
now()
|
||||||
FROM source_info si
|
FROM source_info si
|
||||||
RETURNING id, prop_name, prop_asset_path, layer, is_transferable, is_portable, acquired_at
|
RETURNING id, prop_name, prop_asset_path, layer, is_transferable, is_portable, is_droppable, acquired_at
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
ii.id,
|
ii.id,
|
||||||
|
|
@ -186,6 +215,7 @@ pub async fn pick_up_loose_prop<'e>(
|
||||||
ii.layer,
|
ii.layer,
|
||||||
ii.is_transferable,
|
ii.is_transferable,
|
||||||
ii.is_portable,
|
ii.is_portable,
|
||||||
|
ii.is_droppable,
|
||||||
'server_library'::props.prop_origin as origin,
|
'server_library'::props.prop_origin as origin,
|
||||||
ii.acquired_at
|
ii.acquired_at
|
||||||
FROM inserted_item ii
|
FROM inserted_item ii
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ pub async fn get_server_prop_by_id<'e>(
|
||||||
is_unique,
|
is_unique,
|
||||||
is_transferable,
|
is_transferable,
|
||||||
is_portable,
|
is_portable,
|
||||||
|
is_droppable,
|
||||||
is_active,
|
is_active,
|
||||||
available_from,
|
available_from,
|
||||||
available_until,
|
available_until,
|
||||||
|
|
@ -139,6 +140,7 @@ pub async fn create_server_prop<'e>(
|
||||||
is_unique,
|
is_unique,
|
||||||
is_transferable,
|
is_transferable,
|
||||||
is_portable,
|
is_portable,
|
||||||
|
is_droppable,
|
||||||
is_active,
|
is_active,
|
||||||
available_from,
|
available_from,
|
||||||
available_until,
|
available_until,
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,9 @@ async fn handle_socket(
|
||||||
// needs user_id match anyway)
|
// needs user_id match anyway)
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
|
// Clone pool for use in the receive task (needed for multi-query operations)
|
||||||
|
let recv_pool = pool.clone();
|
||||||
|
|
||||||
// Spawn task to handle incoming messages from client
|
// Spawn task to handle incoming messages from client
|
||||||
let recv_task = tokio::spawn(async move {
|
let recv_task = tokio::spawn(async move {
|
||||||
while let Some(Ok(msg)) = receiver.next().await {
|
while let Some(Ok(msg)) = receiver.next().await {
|
||||||
|
|
@ -401,7 +404,7 @@ async fn handle_socket(
|
||||||
let pos_y = member.position_y + offset_y;
|
let pos_y = member.position_y + offset_y;
|
||||||
|
|
||||||
match loose_props::drop_prop_to_canvas(
|
match loose_props::drop_prop_to_canvas(
|
||||||
&mut *recv_conn,
|
&recv_pool,
|
||||||
inventory_item_id,
|
inventory_item_id,
|
||||||
user_id,
|
user_id,
|
||||||
channel_id,
|
channel_id,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue