202 lines
7.1 KiB
Rust
202 lines
7.1 KiB
Rust
//! Listing Data Access Object (DAO)
|
|
//!
|
|
//! Provides encapsulated CRUD operations for Listing entities
|
|
|
|
use anyhow::Result;
|
|
use chrono::Utc;
|
|
use itertools::Itertools;
|
|
use sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool};
|
|
use std::fmt::Debug;
|
|
|
|
use crate::db::{
|
|
bind_fields::BindFields,
|
|
listing::{
|
|
BasicAuctionFields, BlindAuctionFields, FixedPriceListingFields, Listing, ListingBase,
|
|
ListingFields, MultiSlotAuctionFields, NewListing, PersistedListing,
|
|
PersistedListingFields,
|
|
},
|
|
ListingDbId, ListingType, UserDbId,
|
|
};
|
|
|
|
/// Data Access Object for Listing operations
|
|
#[derive(Clone)]
|
|
pub struct ListingDAO(SqlitePool);
|
|
|
|
impl ListingDAO {
|
|
pub fn new(pool: SqlitePool) -> Self {
|
|
Self(pool)
|
|
}
|
|
|
|
/// Insert a new listing into the database
|
|
pub async fn insert_listing(&self, listing: NewListing) -> Result<PersistedListing> {
|
|
let now = Utc::now();
|
|
|
|
let binds = binds_for_listing(&listing)
|
|
.push("seller_id", &listing.base.seller_id)
|
|
.push("starts_at", &listing.base.starts_at)
|
|
.push("ends_at", &listing.base.ends_at)
|
|
.push("created_at", &now)
|
|
.push("updated_at", &now);
|
|
|
|
let query_str = format!(
|
|
r#"
|
|
INSERT INTO listings ({}) VALUES ({})
|
|
RETURNING *
|
|
"#,
|
|
binds.bind_names().join(", "),
|
|
binds.bind_placeholders().join(", "),
|
|
);
|
|
|
|
let row = binds
|
|
.bind_to_query(sqlx::query(&query_str))
|
|
.fetch_one(&self.0)
|
|
.await?;
|
|
Ok(FromRow::from_row(&row)?)
|
|
}
|
|
|
|
pub async fn update_listing(&self, listing: PersistedListing) -> Result<PersistedListing> {
|
|
let now = Utc::now();
|
|
let binds = binds_for_listing(&listing).push("updated_at", &now);
|
|
|
|
let query_str = format!(
|
|
r#"
|
|
UPDATE listings
|
|
SET {}
|
|
WHERE id = ?
|
|
AND seller_id = ?
|
|
RETURNING *
|
|
"#,
|
|
binds
|
|
.bind_names()
|
|
.map(|name| format!("{name} = ?"))
|
|
.join(", "),
|
|
);
|
|
|
|
let row = binds
|
|
.bind_to_query(sqlx::query(&query_str))
|
|
.bind(listing.persisted.id)
|
|
.bind(listing.base.seller_id)
|
|
.fetch_one(&self.0)
|
|
.await?;
|
|
Ok(FromRow::from_row(&row)?)
|
|
}
|
|
|
|
/// Find a listing by its ID
|
|
pub async fn find_by_id(&self, listing_id: ListingDbId) -> Result<Option<PersistedListing>> {
|
|
let result = sqlx::query_as("SELECT * FROM listings WHERE id = ?")
|
|
.bind(listing_id)
|
|
.fetch_optional(&self.0)
|
|
.await?;
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Find all listings by a seller
|
|
pub async fn find_by_seller(&self, seller_id: UserDbId) -> Result<Vec<PersistedListing>> {
|
|
let rows =
|
|
sqlx::query_as("SELECT * FROM listings WHERE seller_id = ? ORDER BY created_at DESC")
|
|
.bind(seller_id)
|
|
.fetch_all(&self.0)
|
|
.await?;
|
|
Ok(rows)
|
|
}
|
|
|
|
/// Delete a listing
|
|
pub async fn delete_listing(&self, listing_id: ListingDbId) -> Result<()> {
|
|
sqlx::query("DELETE FROM listings WHERE id = ?")
|
|
.bind(listing_id)
|
|
.execute(&self.0)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn binds_for_listing<P: Debug + Clone>(listing: &Listing<P>) -> BindFields {
|
|
BindFields::default()
|
|
.extend(binds_for_base(&listing.base))
|
|
.extend(binds_for_fields(&listing.fields))
|
|
}
|
|
|
|
fn binds_for_base(base: &ListingBase) -> BindFields {
|
|
BindFields::default()
|
|
.push("title", &base.title)
|
|
.push("description", &base.description)
|
|
.push("currency_type", &base.currency_type)
|
|
}
|
|
|
|
fn binds_for_fields(fields: &ListingFields) -> BindFields {
|
|
match fields {
|
|
ListingFields::BasicAuction(fields) => BindFields::default()
|
|
.push("listing_type", &ListingType::BasicAuction)
|
|
.push("starting_bid", &fields.starting_bid)
|
|
.push("buy_now_price", &fields.buy_now_price)
|
|
.push("min_increment", &fields.min_increment)
|
|
.push("anti_snipe_minutes", &fields.anti_snipe_minutes),
|
|
ListingFields::MultiSlotAuction(fields) => BindFields::default()
|
|
.push("listing_type", &ListingType::MultiSlotAuction)
|
|
.push("starting_bid", &fields.starting_bid)
|
|
.push("buy_now_price", &fields.buy_now_price)
|
|
.push("min_increment", &fields.min_increment)
|
|
.push("slots_available", &fields.slots_available)
|
|
.push("anti_snipe_minutes", &fields.anti_snipe_minutes),
|
|
ListingFields::FixedPriceListing(fields) => BindFields::default()
|
|
.push("listing_type", &ListingType::FixedPriceListing)
|
|
.push("buy_now_price", &fields.buy_now_price)
|
|
.push("slots_available", &fields.slots_available),
|
|
ListingFields::BlindAuction(fields) => BindFields::default()
|
|
.push("listing_type", &ListingType::BlindAuction)
|
|
.push("starting_bid", &fields.starting_bid),
|
|
}
|
|
}
|
|
|
|
impl FromRow<'_, SqliteRow> for PersistedListing {
|
|
fn from_row(row: &'_ SqliteRow) -> std::result::Result<Self, sqlx::Error> {
|
|
let listing_type = row.get("listing_type");
|
|
let persisted = PersistedListingFields {
|
|
id: row.get("id"),
|
|
created_at: row.get("created_at"),
|
|
updated_at: row.get("updated_at"),
|
|
};
|
|
let base = ListingBase {
|
|
seller_id: row.get("seller_id"),
|
|
title: row.get("title"),
|
|
description: row.get("description"),
|
|
currency_type: row.get("currency_type"),
|
|
starts_at: row.get("starts_at"),
|
|
ends_at: row.get("ends_at"),
|
|
};
|
|
let fields = match listing_type {
|
|
ListingType::BasicAuction => ListingFields::BasicAuction(BasicAuctionFields {
|
|
starting_bid: row.get("starting_bid"),
|
|
buy_now_price: row.get("buy_now_price"),
|
|
min_increment: row.get("min_increment"),
|
|
anti_snipe_minutes: row.get("anti_snipe_minutes"),
|
|
}),
|
|
ListingType::MultiSlotAuction => {
|
|
ListingFields::MultiSlotAuction(MultiSlotAuctionFields {
|
|
starting_bid: row.get("starting_bid"),
|
|
buy_now_price: row.get("buy_now_price"),
|
|
min_increment: row.get("min_increment"),
|
|
slots_available: row.get("slots_available"),
|
|
anti_snipe_minutes: row.get("anti_snipe_minutes"),
|
|
})
|
|
}
|
|
ListingType::FixedPriceListing => {
|
|
ListingFields::FixedPriceListing(FixedPriceListingFields {
|
|
buy_now_price: row.get("buy_now_price"),
|
|
slots_available: row.get("slots_available"),
|
|
})
|
|
}
|
|
ListingType::BlindAuction => ListingFields::BlindAuction(BlindAuctionFields {
|
|
starting_bid: row.get("starting_bid"),
|
|
}),
|
|
};
|
|
Ok(PersistedListing {
|
|
persisted,
|
|
base,
|
|
fields,
|
|
})
|
|
}
|
|
}
|