296 lines
8.1 KiB
Rust
296 lines
8.1 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
use crate::models::asset::{Asset, AssetType};
|
|
|
|
/// Status of a marketplace listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum ListingStatus {
|
|
Active,
|
|
Sold,
|
|
Cancelled,
|
|
Expired,
|
|
}
|
|
|
|
impl ListingStatus {
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
ListingStatus::Active => "Active",
|
|
ListingStatus::Sold => "Sold",
|
|
ListingStatus::Cancelled => "Cancelled",
|
|
ListingStatus::Expired => "Expired",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of marketplace listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum ListingType {
|
|
FixedPrice,
|
|
Auction,
|
|
Exchange,
|
|
}
|
|
|
|
impl ListingType {
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
ListingType::FixedPrice => "Fixed Price",
|
|
ListingType::Auction => "Auction",
|
|
ListingType::Exchange => "Exchange",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a bid on an auction listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Bid {
|
|
pub id: String,
|
|
pub listing_id: String,
|
|
pub bidder_id: String,
|
|
pub bidder_name: String,
|
|
pub amount: f64,
|
|
pub currency: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub status: BidStatus,
|
|
}
|
|
|
|
/// Status of a bid
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum BidStatus {
|
|
Active,
|
|
Accepted,
|
|
Rejected,
|
|
Cancelled,
|
|
}
|
|
|
|
impl BidStatus {
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
BidStatus::Active => "Active",
|
|
BidStatus::Accepted => "Accepted",
|
|
BidStatus::Rejected => "Rejected",
|
|
BidStatus::Cancelled => "Cancelled",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a marketplace listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Listing {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub asset_id: String,
|
|
pub asset_name: String,
|
|
pub asset_type: AssetType,
|
|
pub seller_id: String,
|
|
pub seller_name: String,
|
|
pub price: f64,
|
|
pub currency: String,
|
|
pub listing_type: ListingType,
|
|
pub status: ListingStatus,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub expires_at: Option<DateTime<Utc>>,
|
|
pub sold_at: Option<DateTime<Utc>>,
|
|
pub buyer_id: Option<String>,
|
|
pub buyer_name: Option<String>,
|
|
pub sale_price: Option<f64>,
|
|
pub bids: Vec<Bid>,
|
|
pub views: u32,
|
|
pub featured: bool,
|
|
pub tags: Vec<String>,
|
|
pub image_url: Option<String>,
|
|
}
|
|
|
|
impl Listing {
|
|
/// Creates a new listing
|
|
pub fn new(
|
|
title: String,
|
|
description: String,
|
|
asset_id: String,
|
|
asset_name: String,
|
|
asset_type: AssetType,
|
|
seller_id: String,
|
|
seller_name: String,
|
|
price: f64,
|
|
currency: String,
|
|
listing_type: ListingType,
|
|
expires_at: Option<DateTime<Utc>>,
|
|
tags: Vec<String>,
|
|
image_url: Option<String>,
|
|
) -> Self {
|
|
let now = Utc::now();
|
|
Self {
|
|
id: format!("listing-{}", Uuid::new_v4().to_string()),
|
|
title,
|
|
description,
|
|
asset_id,
|
|
asset_name,
|
|
asset_type,
|
|
seller_id,
|
|
seller_name,
|
|
price,
|
|
currency,
|
|
listing_type,
|
|
status: ListingStatus::Active,
|
|
created_at: now,
|
|
updated_at: now,
|
|
expires_at,
|
|
sold_at: None,
|
|
buyer_id: None,
|
|
buyer_name: None,
|
|
sale_price: None,
|
|
bids: Vec::new(),
|
|
views: 0,
|
|
featured: false,
|
|
tags,
|
|
image_url,
|
|
}
|
|
}
|
|
|
|
/// Adds a bid to the listing
|
|
pub fn add_bid(&mut self, bidder_id: String, bidder_name: String, amount: f64, currency: String) -> Result<(), String> {
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Listing is not active".to_string());
|
|
}
|
|
|
|
if self.listing_type != ListingType::Auction {
|
|
return Err("Listing is not an auction".to_string());
|
|
}
|
|
|
|
if currency != self.currency {
|
|
return Err(format!("Currency mismatch: expected {}, got {}", self.currency, currency));
|
|
}
|
|
|
|
// Check if bid amount is higher than current highest bid or starting price
|
|
let highest_bid = self.highest_bid();
|
|
let min_bid = match highest_bid {
|
|
Some(bid) => bid.amount,
|
|
None => self.price,
|
|
};
|
|
|
|
if amount <= min_bid {
|
|
return Err(format!("Bid amount must be higher than {}", min_bid));
|
|
}
|
|
|
|
let bid = Bid {
|
|
id: format!("bid-{}", Uuid::new_v4().to_string()),
|
|
listing_id: self.id.clone(),
|
|
bidder_id,
|
|
bidder_name,
|
|
amount,
|
|
currency,
|
|
created_at: Utc::now(),
|
|
status: BidStatus::Active,
|
|
};
|
|
|
|
self.bids.push(bid);
|
|
self.updated_at = Utc::now();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets the highest bid on the listing
|
|
pub fn highest_bid(&self) -> Option<&Bid> {
|
|
self.bids.iter()
|
|
.filter(|b| b.status == BidStatus::Active)
|
|
.max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap())
|
|
}
|
|
|
|
/// Marks the listing as sold
|
|
pub fn mark_as_sold(&mut self, buyer_id: String, buyer_name: String, sale_price: f64) -> Result<(), String> {
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Listing is not active".to_string());
|
|
}
|
|
|
|
self.status = ListingStatus::Sold;
|
|
self.sold_at = Some(Utc::now());
|
|
self.buyer_id = Some(buyer_id);
|
|
self.buyer_name = Some(buyer_name);
|
|
self.sale_price = Some(sale_price);
|
|
self.updated_at = Utc::now();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Cancels the listing
|
|
pub fn cancel(&mut self) -> Result<(), String> {
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Listing is not active".to_string());
|
|
}
|
|
|
|
self.status = ListingStatus::Cancelled;
|
|
self.updated_at = Utc::now();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Increments the view count
|
|
pub fn increment_views(&mut self) {
|
|
self.views += 1;
|
|
}
|
|
|
|
/// Sets the listing as featured
|
|
pub fn set_featured(&mut self, featured: bool) {
|
|
self.featured = featured;
|
|
self.updated_at = Utc::now();
|
|
}
|
|
}
|
|
|
|
/// Statistics for marketplace
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MarketplaceStatistics {
|
|
pub total_listings: usize,
|
|
pub active_listings: usize,
|
|
pub sold_listings: usize,
|
|
pub total_value: f64,
|
|
pub total_sales: f64,
|
|
pub listings_by_type: std::collections::HashMap<String, usize>,
|
|
pub sales_by_asset_type: std::collections::HashMap<String, f64>,
|
|
}
|
|
|
|
impl MarketplaceStatistics {
|
|
pub fn new(listings: &[Listing]) -> Self {
|
|
let mut total_value = 0.0;
|
|
let mut total_sales = 0.0;
|
|
let mut listings_by_type = std::collections::HashMap::new();
|
|
let mut sales_by_asset_type = std::collections::HashMap::new();
|
|
|
|
let active_listings = listings.iter()
|
|
.filter(|l| l.status == ListingStatus::Active)
|
|
.count();
|
|
|
|
let sold_listings = listings.iter()
|
|
.filter(|l| l.status == ListingStatus::Sold)
|
|
.count();
|
|
|
|
for listing in listings {
|
|
if listing.status == ListingStatus::Active {
|
|
total_value += listing.price;
|
|
}
|
|
|
|
if listing.status == ListingStatus::Sold {
|
|
if let Some(sale_price) = listing.sale_price {
|
|
total_sales += sale_price;
|
|
let asset_type = listing.asset_type.as_str().to_string();
|
|
*sales_by_asset_type.entry(asset_type).or_insert(0.0) += sale_price;
|
|
}
|
|
}
|
|
|
|
let listing_type = listing.listing_type.as_str().to_string();
|
|
*listings_by_type.entry(listing_type).or_insert(0) += 1;
|
|
}
|
|
|
|
Self {
|
|
total_listings: listings.len(),
|
|
active_listings,
|
|
sold_listings,
|
|
total_value,
|
|
total_sales,
|
|
listings_by_type,
|
|
sales_by_asset_type,
|
|
}
|
|
}
|
|
}
|