make emotions named instead, add drop prop
This commit is contained in:
parent
989e20757b
commit
ea3b444d71
19 changed files with 1429 additions and 150 deletions
217
crates/chattyness-db/src/queries/loose_props.rs
Normal file
217
crates/chattyness-db/src/queries/loose_props.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
//! Loose props database queries.
|
||||
//!
|
||||
//! Handles props dropped in channels that can be picked up by users.
|
||||
|
||||
use sqlx::PgExecutor;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{InventoryItem, LooseProp};
|
||||
use chattyness_error::AppError;
|
||||
|
||||
/// List all loose props in a channel (excluding expired).
|
||||
pub async fn list_channel_loose_props<'e>(
|
||||
executor: impl PgExecutor<'e>,
|
||||
channel_id: Uuid,
|
||||
) -> Result<Vec<LooseProp>, AppError> {
|
||||
let props = sqlx::query_as::<_, LooseProp>(
|
||||
r#"
|
||||
SELECT
|
||||
lp.id,
|
||||
lp.channel_id,
|
||||
lp.server_prop_id,
|
||||
lp.realm_prop_id,
|
||||
ST_X(lp.position) as position_x,
|
||||
ST_Y(lp.position) as position_y,
|
||||
lp.dropped_by,
|
||||
lp.expires_at,
|
||||
lp.created_at,
|
||||
COALESCE(sp.name, rp.name) as prop_name,
|
||||
COALESCE(sp.asset_path, rp.asset_path) as prop_asset_path
|
||||
FROM props.loose_props lp
|
||||
LEFT JOIN server.props sp ON lp.server_prop_id = sp.id
|
||||
LEFT JOIN props.realm_props rp ON lp.realm_prop_id = rp.id
|
||||
WHERE lp.channel_id = $1
|
||||
AND (lp.expires_at IS NULL OR lp.expires_at > now())
|
||||
ORDER BY lp.created_at ASC
|
||||
"#,
|
||||
)
|
||||
.bind(channel_id)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
|
||||
Ok(props)
|
||||
}
|
||||
|
||||
/// Drop a prop from inventory to the canvas.
|
||||
///
|
||||
/// Deletes from inventory and inserts into loose_props with 30-minute expiry.
|
||||
/// Returns the created loose prop.
|
||||
pub async fn drop_prop_to_canvas<'e>(
|
||||
executor: impl PgExecutor<'e>,
|
||||
inventory_item_id: Uuid,
|
||||
user_id: Uuid,
|
||||
channel_id: Uuid,
|
||||
position_x: f64,
|
||||
position_y: f64,
|
||||
) -> Result<LooseProp, AppError> {
|
||||
// Use a CTE to delete from inventory and insert to loose_props in one query
|
||||
let prop = sqlx::query_as::<_, LooseProp>(
|
||||
r#"
|
||||
WITH deleted_item AS (
|
||||
DELETE FROM props.inventory
|
||||
WHERE id = $1 AND user_id = $2
|
||||
RETURNING id, server_prop_id, realm_prop_id, prop_name, prop_asset_path
|
||||
),
|
||||
inserted_prop AS (
|
||||
INSERT INTO props.loose_props (
|
||||
channel_id,
|
||||
server_prop_id,
|
||||
realm_prop_id,
|
||||
position,
|
||||
dropped_by,
|
||||
expires_at
|
||||
)
|
||||
SELECT
|
||||
$3,
|
||||
di.server_prop_id,
|
||||
di.realm_prop_id,
|
||||
public.make_virtual_point($4::real, $5::real),
|
||||
$2,
|
||||
now() + interval '30 minutes'
|
||||
FROM deleted_item di
|
||||
RETURNING
|
||||
id,
|
||||
channel_id,
|
||||
server_prop_id,
|
||||
realm_prop_id,
|
||||
ST_X(position) as position_x,
|
||||
ST_Y(position) as position_y,
|
||||
dropped_by,
|
||||
expires_at,
|
||||
created_at
|
||||
)
|
||||
SELECT
|
||||
ip.id,
|
||||
ip.channel_id,
|
||||
ip.server_prop_id,
|
||||
ip.realm_prop_id,
|
||||
ip.position_x,
|
||||
ip.position_y,
|
||||
ip.dropped_by,
|
||||
ip.expires_at,
|
||||
ip.created_at,
|
||||
di.prop_name,
|
||||
di.prop_asset_path
|
||||
FROM inserted_prop ip
|
||||
CROSS JOIN deleted_item di
|
||||
"#,
|
||||
)
|
||||
.bind(inventory_item_id)
|
||||
.bind(user_id)
|
||||
.bind(channel_id)
|
||||
.bind(position_x as f32)
|
||||
.bind(position_y as f32)
|
||||
.fetch_optional(executor)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
AppError::NotFound("Inventory item not found or not owned by user".to_string())
|
||||
})?;
|
||||
|
||||
Ok(prop)
|
||||
}
|
||||
|
||||
/// Pick up a loose prop (delete from loose_props, insert to inventory).
|
||||
///
|
||||
/// Returns the created inventory item.
|
||||
pub async fn pick_up_loose_prop<'e>(
|
||||
executor: impl PgExecutor<'e>,
|
||||
loose_prop_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> Result<InventoryItem, AppError> {
|
||||
// Use a CTE to delete from loose_props and insert to inventory
|
||||
let item = sqlx::query_as::<_, InventoryItem>(
|
||||
r#"
|
||||
WITH deleted_prop AS (
|
||||
DELETE FROM props.loose_props
|
||||
WHERE id = $1
|
||||
AND (expires_at IS NULL OR expires_at > now())
|
||||
RETURNING id, server_prop_id, realm_prop_id
|
||||
),
|
||||
source_info AS (
|
||||
SELECT
|
||||
COALESCE(sp.name, rp.name) as prop_name,
|
||||
COALESCE(sp.asset_path, rp.asset_path) as prop_asset_path,
|
||||
COALESCE(sp.default_layer, rp.default_layer) as layer,
|
||||
COALESCE(sp.is_transferable, rp.is_transferable) as is_transferable,
|
||||
COALESCE(sp.is_portable, true) as is_portable,
|
||||
dp.server_prop_id,
|
||||
dp.realm_prop_id
|
||||
FROM deleted_prop dp
|
||||
LEFT JOIN server.props sp ON dp.server_prop_id = sp.id
|
||||
LEFT JOIN props.realm_props rp ON dp.realm_prop_id = rp.id
|
||||
),
|
||||
inserted_item AS (
|
||||
INSERT INTO props.inventory (
|
||||
user_id,
|
||||
server_prop_id,
|
||||
realm_prop_id,
|
||||
prop_name,
|
||||
prop_asset_path,
|
||||
layer,
|
||||
origin,
|
||||
is_transferable,
|
||||
is_portable,
|
||||
provenance,
|
||||
acquired_at
|
||||
)
|
||||
SELECT
|
||||
$2,
|
||||
si.server_prop_id,
|
||||
si.realm_prop_id,
|
||||
si.prop_name,
|
||||
si.prop_asset_path,
|
||||
si.layer,
|
||||
'server_library'::props.prop_origin,
|
||||
COALESCE(si.is_transferable, true),
|
||||
COALESCE(si.is_portable, true),
|
||||
'[]'::jsonb,
|
||||
now()
|
||||
FROM source_info si
|
||||
RETURNING id, prop_name, prop_asset_path, layer, is_transferable, is_portable, acquired_at
|
||||
)
|
||||
SELECT
|
||||
ii.id,
|
||||
ii.prop_name,
|
||||
ii.prop_asset_path,
|
||||
ii.layer,
|
||||
ii.is_transferable,
|
||||
ii.is_portable,
|
||||
'server_library'::props.prop_origin as origin,
|
||||
ii.acquired_at
|
||||
FROM inserted_item ii
|
||||
"#,
|
||||
)
|
||||
.bind(loose_prop_id)
|
||||
.bind(user_id)
|
||||
.fetch_optional(executor)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound("Loose prop not found or has expired".to_string()))?;
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
/// Delete expired loose props.
|
||||
///
|
||||
/// Returns the number of props deleted.
|
||||
pub async fn cleanup_expired_props<'e>(executor: impl PgExecutor<'e>) -> Result<u64, AppError> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM props.loose_props
|
||||
WHERE expires_at IS NOT NULL AND expires_at <= now()
|
||||
"#,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue