Skip to main content

max / makenotwork

2.7 KB · 92 lines History Blame Raw
1 //! Wishlist/bookmark queries: fans save items they want to buy later.
2
3 use chrono::{DateTime, Utc};
4 use sqlx::PgPool;
5
6 use super::{ItemId, UserId};
7 use crate::error::Result;
8
9 /// Check if an item is in the user's wishlist.
10 #[tracing::instrument(skip_all)]
11 pub async fn is_wishlisted(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result<bool> {
12 let exists: bool = sqlx::query_scalar(
13 "SELECT EXISTS(SELECT 1 FROM wishlists WHERE user_id = $1 AND item_id = $2)",
14 )
15 .bind(user_id)
16 .bind(item_id)
17 .fetch_one(pool)
18 .await?;
19
20 Ok(exists)
21 }
22
23 /// Add an item to the user's wishlist (idempotent).
24 #[tracing::instrument(skip_all)]
25 pub async fn add_to_wishlist(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result<()> {
26 sqlx::query(
27 "INSERT INTO wishlists (user_id, item_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
28 )
29 .bind(user_id)
30 .bind(item_id)
31 .execute(pool)
32 .await?;
33
34 Ok(())
35 }
36
37 /// Remove an item from the user's wishlist.
38 #[tracing::instrument(skip_all)]
39 pub async fn remove_from_wishlist(pool: &PgPool, user_id: UserId, item_id: ItemId) -> Result<()> {
40 sqlx::query("DELETE FROM wishlists WHERE user_id = $1 AND item_id = $2")
41 .bind(user_id)
42 .bind(item_id)
43 .execute(pool)
44 .await?;
45
46 Ok(())
47 }
48
49 /// A wishlisted item with joined display data.
50 #[derive(Debug, Clone, sqlx::FromRow)]
51 pub struct WishlistItem {
52 pub item_id: ItemId,
53 pub title: String,
54 pub item_type: String,
55 pub price_cents: i32,
56 pub creator: String,
57 pub added_at: DateTime<Utc>,
58 }
59
60 impl WishlistItem {
61 /// Pre-formatted price string — call this from templates instead of doing
62 /// inline cents-to-dollars math. Wraps the canonical `format_price` helper
63 /// so wishlist rows render the same way as everywhere else in the app
64 /// (handles the "Free" zero case, the whole-dollar case, etc).
65 pub fn price_display(&self) -> String {
66 crate::formatting::format_price(self.price_cents as i64)
67 }
68 }
69
70 /// Get the user's wishlist with item details.
71 #[tracing::instrument(skip_all)]
72 pub async fn get_wishlist(pool: &PgPool, user_id: UserId) -> Result<Vec<WishlistItem>> {
73 let items = sqlx::query_as::<_, WishlistItem>(
74 r#"
75 SELECT w.item_id, i.title, i.item_type::TEXT as item_type, i.price_cents,
76 u.username AS creator, w.created_at AS added_at
77 FROM wishlists w
78 JOIN items i ON i.id = w.item_id
79 JOIN projects p ON p.id = i.project_id
80 JOIN users u ON u.id = p.user_id
81 WHERE w.user_id = $1 AND i.is_public = true AND i.deleted_at IS NULL
82 ORDER BY w.created_at DESC
83 LIMIT 200
84 "#,
85 )
86 .bind(user_id)
87 .fetch_all(pool)
88 .await?;
89
90 Ok(items)
91 }
92