//! Wishlist/bookmark queries: fans save items they want to buy later. use chrono::{DateTime, Utc}; use sqlx::PgPool; use super::{ItemId, UserId}; use crate::error::Result; /// Check if an item is in the user's wishlist. #[tracing::instrument(skip_all)] pub async fn is_wishlisted(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result { let exists: bool = sqlx::query_scalar( "SELECT EXISTS(SELECT 1 FROM wishlists WHERE user_id = $1 AND item_id = $2)", ) .bind(user_id) .bind(item_id) .fetch_one(pool) .await?; Ok(exists) } /// Add an item to the user's wishlist (idempotent). #[tracing::instrument(skip_all)] pub async fn add_to_wishlist(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result<()> { sqlx::query( "INSERT INTO wishlists (user_id, item_id) VALUES ($1, $2) ON CONFLICT DO NOTHING", ) .bind(user_id) .bind(item_id) .execute(pool) .await?; Ok(()) } /// Remove an item from the user's wishlist. #[tracing::instrument(skip_all)] pub async fn remove_from_wishlist(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result<()> { sqlx::query("DELETE FROM wishlists WHERE user_id = $1 AND item_id = $2") .bind(user_id) .bind(item_id) .execute(pool) .await?; Ok(()) } /// A wishlisted item with joined display data. #[derive(Debug, Clone, sqlx::FromRow)] pub struct WishlistItem { pub item_id: ItemId, pub title: String, pub item_type: String, pub price_cents: i32, pub creator: String, pub added_at: DateTime, } impl WishlistItem { /// Pre-formatted price string — call this from templates instead of doing /// inline cents-to-dollars math. Wraps the canonical `format_price` helper /// so wishlist rows render the same way as everywhere else in the app /// (handles the "Free" zero case, the whole-dollar case, etc). pub fn price_display(&self) -> String { crate::formatting::format_price(self.price_cents as i64) } } /// Get the user's wishlist with item details. #[tracing::instrument(skip_all)] pub async fn get_wishlist(pool: &PgPool, user_id: UserId) -> Result> { let items = sqlx::query_as::<_, WishlistItem>( r#" SELECT w.item_id, i.title, i.item_type::TEXT as item_type, i.price_cents, u.username AS creator, w.created_at AS added_at FROM wishlists w JOIN items i ON i.id = w.item_id JOIN projects p ON p.id = i.project_id JOIN users u ON u.id = p.user_id WHERE w.user_id = $1 AND i.is_public = true AND i.deleted_at IS NULL ORDER BY w.created_at DESC LIMIT 200 "#, ) .bind(user_id) .fetch_all(pool) .await?; Ok(items) }