From 0eaab7c743cfdd120d374860433515ea54279c4b Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Wed, 27 Aug 2025 23:23:16 +0000 Subject: [PATCH] refactor: Modularize database models into separate files - Break down monolithic src/db/models.rs into focused model files - Create src/db/models/ directory with individual files: - listing_type.rs - ListingType enum and variants - user.rs - User struct and NewUser helper - listing.rs - Listing struct and NewListing helper - bid.rs - Bid struct and NewBid helper - proxy_bid.rs - ProxyBid struct and NewProxyBid helper - listing_media.rs - ListingMedia for file attachments - user_settings.rs - UserSettings for user preferences - mod.rs - Re-exports all models for seamless access - Remove old monolithic models.rs file - Maintain backward compatibility through re-exports Benefits: - Improved code organization and maintainability - Better separation of concerns for each model - Easier navigation and IDE support - Reduced merge conflicts in team development - Scalable foundation for adding new models --- README.md | 159 +------------------------------- src/db/models.rs | 160 --------------------------------- src/db/models/bid.rs | 38 ++++++++ src/db/models/listing.rs | 48 ++++++++++ src/db/models/listing_media.rs | 15 ++++ src/db/models/listing_type.rs | 16 ++++ src/db/models/mod.rs | 16 ++++ src/db/models/proxy_bid.rs | 25 ++++++ src/db/models/user.rs | 23 +++++ src/db/models/user_settings.rs | 14 +++ 10 files changed, 198 insertions(+), 316 deletions(-) delete mode 100644 src/db/models.rs create mode 100644 src/db/models/bid.rs create mode 100644 src/db/models/listing.rs create mode 100644 src/db/models/listing_media.rs create mode 100644 src/db/models/listing_type.rs create mode 100644 src/db/models/mod.rs create mode 100644 src/db/models/proxy_bid.rs create mode 100644 src/db/models/user.rs create mode 100644 src/db/models/user_settings.rs diff --git a/README.md b/README.md index 4a0d9b1..f61aba6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A Telegram bot for managing various types of auctions, built in Rust using SQLite as the backend database. The bot facilitates auctions between sellers and buyers without handling payments directly. +See a list of user stories for the project under `backlog/docs/user-stories.md`. + ## Core Features ### Listing Types @@ -23,110 +25,6 @@ A Telegram bot for managing various types of auctions, built in Rust using SQLit - **Localization Support**: Multi-language structure (to be implemented) - **Admin Interface**: Separate web interface for moderation (to be implemented) -## Database Schema (SQLite) - -```sql --- Core user tracking -CREATE TABLE users ( - id INTEGER PRIMARY KEY, - telegram_id INTEGER UNIQUE NOT NULL, - username TEXT, - display_name TEXT, - is_banned BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Main listing table (handles all listing types) -CREATE TABLE listings ( - id INTEGER PRIMARY KEY, - seller_id INTEGER NOT NULL, - listing_type TEXT NOT NULL, -- 'basic', 'multi_slot', 'fixed_price', 'blind' - title TEXT NOT NULL, - description TEXT, - - -- Pricing - starting_bid DECIMAL(10,2), - buy_now_price DECIMAL(10,2), - min_increment DECIMAL(10,2) DEFAULT 1.00, - - -- Multi-slot/fixed price - slots_available INTEGER DEFAULT 1, - - -- Timing - starts_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - ends_at TIMESTAMP NOT NULL, - anti_snipe_minutes INTEGER DEFAULT 5, - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (seller_id) REFERENCES users(id) -); - --- Proxy bid strategies (NOT actual bids, but bidding strategies) -CREATE TABLE proxy_bids ( - id INTEGER PRIMARY KEY, - listing_id INTEGER NOT NULL, - buyer_id INTEGER NOT NULL, - max_amount DECIMAL(10,2) NOT NULL, - is_active BOOLEAN DEFAULT TRUE, - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - FOREIGN KEY (listing_id) REFERENCES listings(id), - FOREIGN KEY (buyer_id) REFERENCES users(id), - UNIQUE(listing_id, buyer_id) -- One active proxy per user per listing -); - --- Actual bids that happened (events) -CREATE TABLE bids ( - id INTEGER PRIMARY KEY, - listing_id INTEGER NOT NULL, - buyer_id INTEGER NOT NULL, - bid_amount DECIMAL(10,2) NOT NULL, - - -- For blind listings - description TEXT, - - -- Status - is_cancelled BOOLEAN DEFAULT FALSE, - slot_number INTEGER, -- For multi-slot listings - - -- NULL = manual bid, NOT NULL = generated from proxy - proxy_bid_id INTEGER, - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (listing_id) REFERENCES listings(id), - FOREIGN KEY (buyer_id) REFERENCES users(id), - FOREIGN KEY (proxy_bid_id) REFERENCES proxy_bids(id) -); - --- Media attachments -CREATE TABLE listing_medias ( - id INTEGER PRIMARY KEY, - listing_id INTEGER NOT NULL, - telegram_file_id TEXT NOT NULL, - media_type TEXT NOT NULL, -- 'photo', 'video' - position INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (listing_id) REFERENCES listings(id) -); - --- User preferences -CREATE TABLE user_settings ( - user_id INTEGER PRIMARY KEY, - language_code TEXT DEFAULT 'en', - notify_outbid BOOLEAN DEFAULT TRUE, - notify_won BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) -); -``` - ## Key Design Decisions ### 1. Proxy Bid Architecture @@ -276,46 +174,6 @@ settings - Configure notifications - User management (bans, warnings) - Auction moderation tools -## Common SQL Queries Needed - -```sql --- Get current winning bid for standard auction -SELECT b.*, u.username -FROM bids b -JOIN users u ON b.user_id = u.id -WHERE b.auction_id = ? - AND b.is_cancelled = FALSE -ORDER BY b.bid_amount DESC -LIMIT 1; - --- Get winning bids for multi-slot auction -SELECT b.*, u.username -FROM bids b -JOIN users u ON b.user_id = u.id -WHERE b.auction_id = ? - AND b.is_cancelled = FALSE -ORDER BY b.bid_amount DESC -LIMIT ?; -- slots_available - --- Check if bid was auto-generated -SELECT *, (proxy_bid_id IS NOT NULL) as is_proxy_generated -FROM bids WHERE id = ?; - --- Get active proxy bids for an auction -SELECT * FROM proxy_bids -WHERE auction_id = ? AND is_active = TRUE; - --- Get user's current proxy bid with last generated amount -SELECT - p.*, - b.bid_amount as last_bid_amount -FROM proxy_bids p -LEFT JOIN bids b ON b.proxy_bid_id = p.id -WHERE p.auction_id = ? AND p.user_id = ? AND p.is_active = TRUE -ORDER BY b.created_at DESC -LIMIT 1; -``` - ## Testing Checklist - [ ] User registration on first interaction @@ -331,20 +189,9 @@ LIMIT 1; - [ ] Notification delivery - [ ] Media upload and display -## Deployment Notes - -- Use systemd service for production -- Set up automatic restarts -- Configure log rotation -- Use separate database file for production -- Regular database backups -- Monitor Telegram API rate limits - -## API Rate Limits to Consider +## Telegram API Rate Limits to Consider - Telegram allows 30 messages/second to different users - 20 messages/minute to same user - Bulk notifications should be queued and throttled - Edit message updates should be debounced - -This README contains the complete context of the project. All design decisions have been made, the schema is finalized, and the basic structure is in place. The next step is implementing the core business logic for auctions and bidding. \ No newline at end of file diff --git a/src/db/models.rs b/src/db/models.rs deleted file mode 100644 index dcd0522..0000000 --- a/src/db/models.rs +++ /dev/null @@ -1,160 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::FromRow; - -use super::money_amount::MoneyAmount; - -/// Types of listings supported by the platform -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] -#[sqlx(type_name = "TEXT")] -#[sqlx(rename_all = "lowercase")] -pub enum ListingType { - /// Traditional time-based auction with bidding - Standard, - /// Auction with multiple winners/slots available - MultiSlot, - /// Fixed price sale with no bidding - FixedPrice, - /// Blind auction where seller chooses winner - Blind, -} - -/// Core user information -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct User { - pub id: i64, - pub telegram_id: i64, - pub username: Option, - pub display_name: Option, - pub is_banned: bool, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// Main listing/auction entity -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct Listing { - pub id: i64, - pub seller_id: i64, - pub listing_type: ListingType, - pub title: String, - pub description: Option, - - // Pricing fields - pub starting_bid: Option, - pub buy_now_price: Option, - pub min_increment: MoneyAmount, - - // Multi-slot/fixed price - pub slots_available: i32, - - // Timing - pub starts_at: DateTime, - pub ends_at: DateTime, - pub anti_snipe_minutes: i32, - - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// Proxy bid strategies (automatic bidding settings) -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct ProxyBid { - pub id: i64, - pub listing_id: i64, - pub buyer_id: i64, - pub max_amount: MoneyAmount, - pub is_active: bool, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// Actual bids placed on listings -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct Bid { - pub id: i64, - pub listing_id: i64, - pub buyer_id: i64, - pub bid_amount: MoneyAmount, - - // For blind listings - pub description: Option, - - // Status - pub is_cancelled: bool, - pub slot_number: Option, // For multi-slot listings - - // Reference to proxy bid if auto-generated - pub proxy_bid_id: Option, - - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// Media attachments for listings -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct ListingMedia { - pub id: i64, - pub listing_id: i64, - pub telegram_file_id: String, - pub media_type: String, // 'photo' or 'video' - pub position: i32, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// User preferences and settings -#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] -pub struct UserSettings { - pub user_id: i64, - pub language_code: String, - pub notify_outbid: bool, - pub notify_won: bool, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -// Helper structs for database operations - -/// New user data for insertion -#[derive(Debug, Clone)] -pub struct NewUser { - pub telegram_id: i64, - pub username: Option, - pub display_name: Option, -} - -/// New listing data for insertion -#[derive(Debug, Clone)] -pub struct NewListing { - pub seller_id: i64, - pub listing_type: ListingType, - pub title: String, - pub description: Option, - pub starting_bid: Option, - pub buy_now_price: Option, - pub min_increment: MoneyAmount, - pub slots_available: i32, - pub starts_at: DateTime, - pub ends_at: DateTime, - pub anti_snipe_minutes: i32, -} - -/// New bid data for insertion -#[derive(Debug, Clone)] -pub struct NewBid { - pub listing_id: i64, - pub buyer_id: i64, - pub bid_amount: MoneyAmount, - pub description: Option, - pub slot_number: Option, - pub proxy_bid_id: Option, -} - -/// New proxy bid data for insertion -#[derive(Debug, Clone)] -pub struct NewProxyBid { - pub listing_id: i64, - pub buyer_id: i64, - pub max_amount: MoneyAmount, -} diff --git a/src/db/models/bid.rs b/src/db/models/bid.rs new file mode 100644 index 0000000..2f8493c --- /dev/null +++ b/src/db/models/bid.rs @@ -0,0 +1,38 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +use crate::db::money_amount::MoneyAmount; + +/// Actual bids placed on listings +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct Bid { + pub id: i64, + pub listing_id: i64, + pub buyer_id: i64, + pub bid_amount: MoneyAmount, + + // For blind listings + pub description: Option, + + // Status + pub is_cancelled: bool, + pub slot_number: Option, // For multi-slot listings + + // Reference to proxy bid if auto-generated + pub proxy_bid_id: Option, + + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// New bid data for insertion +#[derive(Debug, Clone)] +pub struct NewBid { + pub listing_id: i64, + pub buyer_id: i64, + pub bid_amount: MoneyAmount, + pub description: Option, + pub slot_number: Option, + pub proxy_bid_id: Option, +} diff --git a/src/db/models/listing.rs b/src/db/models/listing.rs new file mode 100644 index 0000000..b946342 --- /dev/null +++ b/src/db/models/listing.rs @@ -0,0 +1,48 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +use super::listing_type::ListingType; +use crate::db::money_amount::MoneyAmount; + +/// Main listing/auction entity +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct Listing { + pub id: i64, + pub seller_id: i64, + pub listing_type: ListingType, + pub title: String, + pub description: Option, + + // Pricing fields + pub starting_bid: Option, + pub buy_now_price: Option, + pub min_increment: MoneyAmount, + + // Multi-slot/fixed price + pub slots_available: i32, + + // Timing + pub starts_at: DateTime, + pub ends_at: DateTime, + pub anti_snipe_minutes: i32, + + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// New listing data for insertion +#[derive(Debug, Clone)] +pub struct NewListing { + pub seller_id: i64, + pub listing_type: ListingType, + pub title: String, + pub description: Option, + pub starting_bid: Option, + pub buy_now_price: Option, + pub min_increment: MoneyAmount, + pub slots_available: i32, + pub starts_at: DateTime, + pub ends_at: DateTime, + pub anti_snipe_minutes: i32, +} diff --git a/src/db/models/listing_media.rs b/src/db/models/listing_media.rs new file mode 100644 index 0000000..da574e7 --- /dev/null +++ b/src/db/models/listing_media.rs @@ -0,0 +1,15 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +/// Media attachments for listings +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct ListingMedia { + pub id: i64, + pub listing_id: i64, + pub telegram_file_id: String, + pub media_type: String, // 'photo' or 'video' + pub position: i32, + pub created_at: DateTime, + pub updated_at: DateTime, +} diff --git a/src/db/models/listing_type.rs b/src/db/models/listing_type.rs new file mode 100644 index 0000000..192ebb2 --- /dev/null +++ b/src/db/models/listing_type.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Types of listings supported by the platform +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] +#[sqlx(type_name = "TEXT")] +#[sqlx(rename_all = "lowercase")] +pub enum ListingType { + /// Traditional time-based auction with bidding + Standard, + /// Auction with multiple winners/slots available + MultiSlot, + /// Fixed price sale with no bidding + FixedPrice, + /// Blind auction where seller chooses winner + Blind, +} diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs new file mode 100644 index 0000000..bdcc874 --- /dev/null +++ b/src/db/models/mod.rs @@ -0,0 +1,16 @@ +pub mod bid; +pub mod listing; +pub mod listing_media; +pub mod listing_type; +pub mod proxy_bid; +pub mod user; +pub mod user_settings; + +// Re-export all types for easy access +pub use bid::*; +pub use listing::*; +pub use listing_media::*; +pub use listing_type::*; +pub use proxy_bid::*; +pub use user::*; +pub use user_settings::*; diff --git a/src/db/models/proxy_bid.rs b/src/db/models/proxy_bid.rs new file mode 100644 index 0000000..9bb4448 --- /dev/null +++ b/src/db/models/proxy_bid.rs @@ -0,0 +1,25 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +use crate::db::money_amount::MoneyAmount; + +/// Proxy bid strategies (automatic bidding settings) +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct ProxyBid { + pub id: i64, + pub listing_id: i64, + pub buyer_id: i64, + pub max_amount: MoneyAmount, + pub is_active: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// New proxy bid data for insertion +#[derive(Debug, Clone)] +pub struct NewProxyBid { + pub listing_id: i64, + pub buyer_id: i64, + pub max_amount: MoneyAmount, +} diff --git a/src/db/models/user.rs b/src/db/models/user.rs new file mode 100644 index 0000000..a065177 --- /dev/null +++ b/src/db/models/user.rs @@ -0,0 +1,23 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +/// Core user information +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct User { + pub id: i64, + pub telegram_id: i64, + pub username: Option, + pub display_name: Option, + pub is_banned: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// New user data for insertion +#[derive(Debug, Clone)] +pub struct NewUser { + pub telegram_id: i64, + pub username: Option, + pub display_name: Option, +} diff --git a/src/db/models/user_settings.rs b/src/db/models/user_settings.rs new file mode 100644 index 0000000..a2abe59 --- /dev/null +++ b/src/db/models/user_settings.rs @@ -0,0 +1,14 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +/// User preferences and settings +#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] +pub struct UserSettings { + pub user_id: i64, + pub language_code: String, + pub notify_outbid: bool, + pub notify_won: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +}