313 lines
9.6 KiB
Rust
313 lines
9.6 KiB
Rust
// heromodels/src/models/finance/marketplace.rs
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use rhai::{CustomType, TypeBuilder};
|
|
use chrono::{DateTime, Utc};
|
|
use heromodels_core::BaseModelData;
|
|
use heromodels_derive::model;
|
|
|
|
use super::asset::AssetType;
|
|
|
|
/// ListingStatus defines the status of a marketplace listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum ListingStatus {
|
|
Active, // Listing is active and available
|
|
Sold, // Listing has been sold
|
|
Cancelled, // Listing was cancelled by the seller
|
|
Expired, // Listing has expired
|
|
}
|
|
|
|
impl Default for ListingStatus {
|
|
fn default() -> Self {
|
|
ListingStatus::Active
|
|
}
|
|
}
|
|
|
|
/// ListingType defines the type of marketplace listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum ListingType {
|
|
FixedPrice, // Fixed price sale
|
|
Auction, // Auction with bids
|
|
Exchange, // Exchange for other assets
|
|
}
|
|
|
|
impl Default for ListingType {
|
|
fn default() -> Self {
|
|
ListingType::FixedPrice
|
|
}
|
|
}
|
|
|
|
/// BidStatus defines the status of a bid on an auction listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum BidStatus {
|
|
Active, // Bid is active
|
|
Accepted, // Bid was accepted
|
|
Rejected, // Bid was rejected
|
|
Cancelled, // Bid was cancelled by the bidder
|
|
}
|
|
|
|
impl Default for BidStatus {
|
|
fn default() -> Self {
|
|
BidStatus::Active
|
|
}
|
|
}
|
|
|
|
/// Bid represents a bid on an auction listing
|
|
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
|
pub struct Bid {
|
|
pub listing_id: String, // ID of the listing this bid belongs to
|
|
pub bidder_id: u32, // ID of the user who placed the bid
|
|
pub amount: f64, // Bid amount
|
|
pub currency: String, // Currency of the bid
|
|
pub status: BidStatus, // Status of the bid
|
|
pub created_at: DateTime<Utc>, // When the bid was created
|
|
}
|
|
|
|
impl Bid {
|
|
/// Create a new bid
|
|
pub fn new(
|
|
listing_id: impl ToString,
|
|
bidder_id: u32,
|
|
amount: f64,
|
|
currency: impl ToString,
|
|
) -> Self {
|
|
Self {
|
|
listing_id: listing_id.to_string(),
|
|
bidder_id,
|
|
amount,
|
|
currency: currency.to_string(),
|
|
status: BidStatus::default(),
|
|
created_at: Utc::now(),
|
|
}
|
|
}
|
|
|
|
/// Update the status of the bid
|
|
pub fn update_status(mut self, status: BidStatus) -> Self {
|
|
self.status = status;
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Listing represents a marketplace listing for an asset
|
|
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
|
#[model] // Has base.Base in V spec
|
|
pub struct Listing {
|
|
pub base_data: BaseModelData,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub asset_id: String,
|
|
pub asset_type: AssetType,
|
|
pub seller_id: String,
|
|
pub price: f64, // Initial price for fixed price, or starting price for auction
|
|
pub currency: String,
|
|
pub listing_type: ListingType,
|
|
pub status: ListingStatus,
|
|
pub expires_at: Option<DateTime<Utc>>, // Optional expiration date
|
|
pub sold_at: Option<DateTime<Utc>>, // Optional date when the item was sold
|
|
pub buyer_id: Option<String>, // Optional buyer ID
|
|
pub sale_price: Option<f64>, // Optional final sale price
|
|
pub bids: Vec<Bid>, // List of bids for auction type listings
|
|
pub tags: Vec<String>, // Tags for the listing
|
|
pub image_url: Option<String>, // Optional image URL
|
|
}
|
|
|
|
impl Listing {
|
|
/// Create a new listing with auto-generated ID
|
|
///
|
|
/// # Arguments
|
|
/// * `id` - Optional ID for the listing (use None for auto-generated ID)
|
|
/// * `title` - Title of the listing
|
|
/// * `description` - Description of the listing
|
|
/// * `asset_id` - ID of the asset being listed
|
|
/// * `asset_type` - Type of the asset
|
|
/// * `seller_id` - ID of the seller
|
|
/// * `price` - Initial price for fixed price, or starting price for auction
|
|
/// * `currency` - Currency of the price
|
|
/// * `listing_type` - Type of the listing
|
|
/// * `expires_at` - Optional expiration date
|
|
/// * `tags` - Tags for the listing
|
|
/// * `image_url` - Optional image URL
|
|
pub fn new(
|
|
id: Option<u32>,
|
|
title: impl ToString,
|
|
description: impl ToString,
|
|
asset_id: impl ToString,
|
|
asset_type: AssetType,
|
|
seller_id: impl ToString,
|
|
price: f64,
|
|
currency: impl ToString,
|
|
listing_type: ListingType,
|
|
expires_at: Option<DateTime<Utc>>,
|
|
tags: Vec<String>,
|
|
image_url: Option<impl ToString>,
|
|
) -> Self {
|
|
let mut base_data = BaseModelData::new();
|
|
if let Some(id) = id {
|
|
base_data.update_id(id);
|
|
}
|
|
|
|
Self {
|
|
base_data,
|
|
title: title.to_string(),
|
|
description: description.to_string(),
|
|
asset_id: asset_id.to_string(),
|
|
asset_type,
|
|
seller_id: seller_id.to_string(),
|
|
price,
|
|
currency: currency.to_string(),
|
|
listing_type,
|
|
status: ListingStatus::default(),
|
|
expires_at,
|
|
sold_at: None,
|
|
buyer_id: None,
|
|
sale_price: None,
|
|
bids: Vec::new(),
|
|
tags,
|
|
image_url: image_url.map(|url| url.to_string()),
|
|
}
|
|
}
|
|
|
|
/// Add a bid to an auction listing
|
|
pub fn add_bid(mut self, bid: Bid) -> Result<Self, &'static str> {
|
|
// Check if listing is an auction
|
|
if self.listing_type != ListingType::Auction {
|
|
return Err("Bids can only be placed on auction listings");
|
|
}
|
|
|
|
// Check if listing is active
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Cannot place bid on inactive listing");
|
|
}
|
|
|
|
// Check if bid amount is higher than current price
|
|
if bid.amount <= self.price {
|
|
return Err("Bid amount must be higher than current price");
|
|
}
|
|
|
|
// Check if there are existing bids and if the new bid is higher
|
|
if let Some(highest_bid) = self.highest_bid() {
|
|
if bid.amount <= highest_bid.amount {
|
|
return Err("Bid amount must be higher than current highest bid");
|
|
}
|
|
}
|
|
|
|
// Add the bid
|
|
self.bids.push(bid);
|
|
|
|
// Update the current price to the new highest bid
|
|
if let Some(highest_bid) = self.highest_bid() {
|
|
self.price = highest_bid.amount;
|
|
}
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Get the highest active bid
|
|
pub fn highest_bid(&self) -> Option<&Bid> {
|
|
self.bids
|
|
.iter()
|
|
.filter(|bid| bid.status == BidStatus::Active)
|
|
.max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap())
|
|
}
|
|
|
|
/// Complete a sale (fixed price or auction)
|
|
pub fn complete_sale(
|
|
mut self,
|
|
buyer_id: impl ToString,
|
|
sale_price: f64,
|
|
) -> Result<Self, &'static str> {
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Cannot complete sale for inactive listing");
|
|
}
|
|
|
|
self.status = ListingStatus::Sold;
|
|
self.buyer_id = Some(buyer_id.to_string());
|
|
self.sale_price = Some(sale_price);
|
|
self.sold_at = Some(Utc::now());
|
|
|
|
// If this was an auction, accept the winning bid and reject others
|
|
if self.listing_type == ListingType::Auction {
|
|
for bid in &mut self.bids {
|
|
if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string()
|
|
&& bid.amount == sale_price
|
|
{
|
|
bid.status = BidStatus::Accepted;
|
|
} else {
|
|
bid.status = BidStatus::Rejected;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Cancel the listing
|
|
pub fn cancel(mut self) -> Result<Self, &'static str> {
|
|
if self.status != ListingStatus::Active {
|
|
return Err("Cannot cancel inactive listing");
|
|
}
|
|
|
|
self.status = ListingStatus::Cancelled;
|
|
|
|
// Cancel all active bids
|
|
for bid in &mut self.bids {
|
|
if bid.status == BidStatus::Active {
|
|
bid.status = BidStatus::Cancelled;
|
|
}
|
|
}
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Check if the listing has expired and update status if needed
|
|
pub fn check_expiration(mut self) -> Self {
|
|
if self.status == ListingStatus::Active {
|
|
if let Some(expires_at) = self.expires_at {
|
|
if Utc::now() > expires_at {
|
|
self.status = ListingStatus::Expired;
|
|
|
|
// Cancel all active bids
|
|
for bid in &mut self.bids {
|
|
if bid.status == BidStatus::Active {
|
|
bid.status = BidStatus::Cancelled;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self
|
|
}
|
|
|
|
/// Add tags to the listing
|
|
pub fn add_tags(mut self, tags: Vec<impl ToString>) -> Self {
|
|
for tag in tags {
|
|
self.tags.push(tag.to_string());
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Update the listing details
|
|
pub fn update_details(
|
|
mut self,
|
|
title: Option<impl ToString>,
|
|
description: Option<impl ToString>,
|
|
price: Option<f64>,
|
|
image_url: Option<impl ToString>,
|
|
) -> Self {
|
|
if let Some(title) = title {
|
|
self.title = title.to_string();
|
|
}
|
|
if let Some(description) = description {
|
|
self.description = description.to_string();
|
|
}
|
|
if let Some(price) = price {
|
|
self.price = price;
|
|
}
|
|
if let Some(image_url) = image_url {
|
|
self.image_url = Some(image_url.to_string());
|
|
}
|
|
self
|
|
}
|
|
}
|