exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

ratings.rs (2398B)


      1 //! Rating repository functions with OpenTelemetry instrumentation
      2 //!
      3 //! This module contains database operations for product ratings,
      4 //! separated from HTTP handlers.
      5 
      6 use sqlx::PgPool;
      7 use tracing::instrument;
      8 use uuid::Uuid;
      9 
     10 use crate::models::{Rating, UpsertRatingRequest};
     11 
     12 /// Check if a product exists and is not deleted
     13 #[instrument(
     14     name = "SELECT products",
     15     skip(pool),
     16     fields(
     17         db.system.name = "postgresql",
     18         db.namespace = "products",
     19         db.operation.name = "SELECT",
     20         db.collection.name = "products",
     21         db.query.text = "SELECT EXISTS(SELECT 1 FROM products WHERE uuid = $1 AND deleted_at IS NULL)",
     22         otelmart.product.uuid = %product_uuid
     23     )
     24 )]
     25 pub async fn product_exists(pool: &PgPool, product_uuid: Uuid) -> Result<bool, sqlx::Error> {
     26     let result: Option<(bool,)> = sqlx::query_as(
     27         "SELECT EXISTS(SELECT 1 FROM products WHERE uuid = $1 AND deleted_at IS NULL)",
     28     )
     29     .bind(product_uuid)
     30     .fetch_optional(pool)
     31     .await?;
     32 
     33     Ok(result.map(|x| x.0).unwrap_or(false))
     34 }
     35 
     36 /// Create or update a product rating
     37 ///
     38 /// Uses PostgreSQL's ON CONFLICT clause for atomic upsert.
     39 /// The unique constraint on (product_id, user_id) ensures one rating per user per product.
     40 #[instrument(
     41     name = "UPSERT ratings",
     42     skip(pool, payload),
     43     fields(
     44         db.system.name = "postgresql",
     45         db.namespace = "products",
     46         db.operation.name = "INSERT",
     47         db.collection.name = "ratings",
     48         db.query.text = "INSERT INTO ratings (...) VALUES (...) ON CONFLICT DO UPDATE ... RETURNING *",
     49         otelmart.product.uuid = %product_uuid,
     50         otelmart.rating.value = payload.rating
     51     )
     52 )]
     53 pub async fn upsert_rating(
     54     pool: &PgPool,
     55     product_uuid: Uuid,
     56     payload: &UpsertRatingRequest,
     57 ) -> Result<Rating, sqlx::Error> {
     58     sqlx::query_as::<_, Rating>(
     59         r#"
     60         INSERT INTO ratings (product_id, user_id, rating, review)
     61         VALUES ($1, $2, $3, $4)
     62         ON CONFLICT (product_id, user_id)
     63         DO UPDATE SET
     64             rating = EXCLUDED.rating,
     65             review = EXCLUDED.review,
     66             updated_at = NOW()
     67         RETURNING id, uuid, product_id, user_id, rating, review, created_at, updated_at
     68         "#,
     69     )
     70     .bind(product_uuid)
     71     .bind(payload.user_id)
     72     .bind(payload.rating)
     73     .bind(&payload.review)
     74     .fetch_one(pool)
     75     .await
     76 }