implement marketplace feature wip
This commit is contained in:
295
actix_mvc_app/src/models/marketplace.rs
Normal file
295
actix_mvc_app/src/models/marketplace.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,8 +6,10 @@ pub mod governance;
|
||||
pub mod flow;
|
||||
pub mod contract;
|
||||
pub mod asset;
|
||||
pub mod marketplace;
|
||||
|
||||
// Re-export models for easier imports
|
||||
pub use user::User;
|
||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority};
|
||||
pub use calendar::{CalendarEvent, CalendarViewMode};
|
||||
pub use marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus, MarketplaceStatistics};
|
||||
|
Reference in New Issue
Block a user