add .env.example, user-stories.md

This commit is contained in:
Dylan Knutson
2025-08-27 19:12:40 +00:00
parent 947092ce54
commit 30751181e4
24 changed files with 4191 additions and 4 deletions

52
.env.example Normal file
View File

@@ -0,0 +1,52 @@
# ==================================
# Pawctioneer Bot Configuration
# ==================================
# Copy this file to .env and fill in your values
# Usage: cp .env.example .env
# ==== REQUIRED SETTINGS ====
# Your Telegram bot token from @BotFather
# Get one by messaging @BotFather on Telegram and creating a new bot
TELOXIDE_TOKEN=your_bot_token_here
# ==== OPTIONAL SETTINGS ====
# Database file location (SQLite)
# Default: sqlite:pawctioneer_bot.db
# You can use an absolute path: /path/to/your/database.db
DATABASE_URL=sqlite:pawctioneer_bot.db
# Your Telegram user ID for admin commands
# You can get this by messaging @userinfobot on Telegram
# Uncomment and set your user ID to enable admin features
# ADMIN_USER_ID=123456789
# Port for the web admin interface (future feature)
# Default: 3000
WEB_PORT=3000
# ==== LOGGING SETTINGS ====
# Logging level configuration
# Options: error, warn, info, debug, trace
# You can set different levels for different modules:
# Examples:
# RUST_LOG=info # All modules at info level
# RUST_LOG=debug # All modules at debug level
# RUST_LOG=info,pawctioneer_bot=debug # Most modules info, this bot debug
# RUST_LOG=warn,teloxide=info # Most modules warn, teloxide info
RUST_LOG=info
# Timezone (optional, uses system timezone if not set)
# Examples: UTC, America/New_York, Europe/London, Asia/Tokyo
# TZ=UTC
# ==== DEVELOPMENT SETTINGS ====
# These settings are primarily for development and testing
# Enable more verbose teloxide logging (uncomment for debugging)
# RUST_LOG=debug,teloxide=debug
# Database with more detailed logging (uncomment for SQL debugging)
# RUST_LOG=debug,sqlx=debug

2
.gitignore vendored
View File

@@ -1 +1,3 @@
/target
!.env.example
.env

3570
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,21 @@
[package]
name = "pawctioneer-bot"
version = "0.1.0"
edition = "2024"
edition = "2021"
[dependencies]
teloxide = { version = "0.17.0", features = ["macros"] }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
sqlx = { version = "0.8.6", features = [
"runtime-tokio-rustls",
"sqlite",
"chrono",
"rust_decimal",
] }
rust_decimal = { version = "1.33", features = ["serde"] }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
env_logger = "0.11.8"
anyhow = "1.0"
dotenvy = "0.15"

View File

@@ -245,7 +245,6 @@ settings - Configure notifications
- **sqlx**: Compile-time checked SQL queries, async support
- **tokio**: Industry standard async runtime
- **rust_decimal**: Proper decimal handling for money
- **chrono**: Timezone-aware datetime handling
## Next Implementation Steps (Priority Order)

View File

@@ -0,0 +1,64 @@
## Types of listings:
- Fixed price
- Basic auction
- Multi-slot auction
- Blind auction
## User stories for listing types:
### Fixed price
- As a seller,
- I can specify a fixed price.
- I can set a number of items / slots available for sale.
- I can edit the number of items / slots available for sale.
### Basic auction
- As a seller,
- I can create a basic auction with a starting price.
- I can specify the minimum price increment.
### Multi-slot auction
- As a seller,
- I can specify separate slots for an auction.
- I can specify separate starting prices on each slot.
- As a buyer,
- I can submit a bid contingent on mutliple slots
### Blind auction
- As a seller,
- I can specify a starting price.
- I can see submitted bids so far.
- I can end the auction early when I have a bid I am happy with.
- As a buyer,
- I can submit a bid with a price and a description.
- I can resubmit my bid with a higher price or revised description.
## User stories for all listing types:
- As a seller,
- I can create different listing types via the Pawctionerr telegram bot.
- I can specify photos and videos to be shown on the listing post.
- I can edit listings by adding and removing new photos and videos.
- I can edit listings by supplying a new description text.
- I can mark a won / purchased listing as paid for.
- I can view won / purchased listings by status (paid for, pending).
## User stories for auctions:
- As a seller,
- I can specify a starting date and time (or now).
- I can specify a duration.
- I can register for notifications when a bid is submitted.
- I can see a list of bids submitted so far.
- I can void bids.
- As a buyer,
- I can see the current high bid.
- I cannot see the name of the high bidder unless it is me.
- I can clearly see if I am the highest bidder.
- I can request my bid be voided.
## User stories for non-blind auctions:
- As a seller,
- I can specify an auto-buy price.
- I can specify an anti-sniping period.
- As a buyer,
- I can register for a notification when an artist starts a new auction.
- I can register for a notification when I am outbid.
- I can register for a notification when an auction ends.

View File

@@ -0,0 +1,21 @@
---
id: task-001
title: Set up database connection and SQLx integration
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Integrate SQLx with the existing configuration system to establish database connections. This includes setting up connection pooling and integrating with the Config struct.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Database connection pool is created using Config.database_url
- [ ] #2 Connection health check function is implemented
- [ ] #3 Database connection is properly integrated with main.rs
- [ ] #4 Error handling for database connection failures is implemented
<!-- AC:END -->

View File

@@ -0,0 +1,21 @@
---
id: task-002
title: Create database migration system
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Set up SQLx migrations infrastructure to manage database schema versions. Create the migrations directory structure and initial migration files.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 migrations/ directory is created with proper structure
- [ ] #2 001_initial_schema.sql migration file contains complete schema
- [ ] #3 Migration runner is integrated with application startup
- [ ] #4 Database schema matches the design specified in README.md
<!-- AC:END -->

View File

@@ -0,0 +1,24 @@
---
id: task-003
title: Create core database models and structs
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Define Rust structs and enums that represent the database schema entities. This includes User, Auction, Bid, ProxyBid, AuctionMedia, and UserSettings models with proper SQLx derive macros.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 User struct with all fields from users table is defined
- [ ] #2 Auction struct with AuctionType enum is defined
- [ ] #3 Bid and ProxyBid structs are defined with proper relationships
- [ ] #4 AuctionMedia and UserSettings structs are defined
- [ ] #5 All models have proper SQLx derive macros (FromRow, Type, etc.)
- [ ] #6 Rust Decimal is used for monetary fields
- [ ] #7 Chrono DateTime is used for timestamp fields
<!-- AC:END -->

View File

@@ -0,0 +1,22 @@
---
id: task-004
title: Implement user management database operations
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Create database operations for user registration, lookup, and management. This includes automatic user registration on first bot interaction and user preference management.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 User registration function creates new users from Telegram data
- [ ] #2 User lookup by telegram_id function is implemented
- [ ] #3 User settings creation with default values is implemented
- [ ] #4 User ban/unban functionality is implemented
- [ ] #5 User profile update functionality is implemented
<!-- AC:END -->

View File

@@ -0,0 +1,23 @@
---
id: task-005
title: Implement auction database operations
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Create database operations for auction management including creation, retrieval, updates, and media handling. Support all four auction types: standard, multi-slot, fixed-price, and blind auctions.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Auction creation function supports all four auction types
- [ ] #2 Auction retrieval by ID and by seller functions are implemented
- [ ] #3 Auction update functions (status, end time, etc.) are implemented
- [ ] #4 Active auctions listing with pagination is implemented
- [ ] #5 Auction media attachment functions are implemented
- [ ] #6 Auction search and filtering functionality is implemented
<!-- AC:END -->

View File

@@ -0,0 +1,24 @@
---
id: task-006
title: Implement bidding system database operations
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Create database operations for the bidding system including manual bids, proxy bids, and bid history tracking. This is critical for the auction functionality.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Manual bid placement function with validation is implemented
- [ ] #2 Proxy bid creation and management functions are implemented
- [ ] #3 Bid history retrieval functions are implemented
- [ ] #4 Winning bid calculation functions are implemented
- [ ] #5 Multi-slot auction winner selection is implemented
- [ ] #6 Bid cancellation functionality is implemented
- [ ] #7 Anti-snipe time extension logic is implemented
<!-- AC:END -->

View File

@@ -0,0 +1,24 @@
---
id: task-007
title: Create database integration tests
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Build comprehensive test suite for all database operations to ensure data integrity and proper functionality. Include edge cases and error scenarios.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 User management tests cover registration and lookup scenarios
- [ ] #2 Auction operation tests cover all auction types
- [ ] #3 Bidding system tests cover manual and proxy bids
- [ ] #4 Multi-slot auction tests verify winner selection logic
- [ ] #5 Database constraint tests verify foreign key relationships
- [ ] #6 Error handling tests for invalid data scenarios
- [ ] #7 Performance tests for database operations under load
<!-- AC:END -->

View File

@@ -0,0 +1,23 @@
---
id: task-008
title: Integrate database with bot commands
status: To Do
assignee: []
created_date: '2025-08-27 18:31'
labels: []
dependencies: []
---
## Description
Connect the database operations with the existing bot command handlers to enable real functionality. Replace stub implementations with actual database-backed operations.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Start command registers new users automatically
- [ ] #2 Settings command integrates with user_settings table
- [ ] #3 MyAuctions command displays user's actual auctions
- [ ] #4 MyBids command shows user's actual bid history
- [ ] #5 Error handling for database operations in commands
- [ ] #6 User context is properly passed to database operations
<!-- AC:END -->

View File

@@ -0,0 +1,24 @@
---
id: task-009
title: Create database module structure
status: To Do
assignee: []
created_date: '2025-08-27 18:32'
labels: []
dependencies: []
---
## Description
Organize database-related code into a proper module structure with separation of concerns. Create db module with sub-modules for different entity operations.
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 src/db/mod.rs module is created with proper exports
- [ ] #2 src/db/models.rs contains all database model definitions
- [ ] #3 src/db/users.rs contains user-related database operations
- [ ] #4 src/db/auctions.rs contains auction-related database operations
- [ ] #5 src/db/bids.rs contains bidding-related database operations
- [ ] #6 src/db/connection.rs handles database connection management
- [ ] #7 Database module is integrated with main.rs and config system
<!-- AC:END -->

15
src/commands/help.rs Normal file
View File

@@ -0,0 +1,15 @@
use teloxide::{prelude::*, types::Message, utils::command::BotCommands, Bot};
use crate::Command;
pub async fn handle_help(bot: Bot, msg: Message) -> ResponseResult<()> {
let help_message = format!(
"📋 Available Commands:\n\n{}\n\n\
📧 Support: Contact @admin for help\n\
🔗 More info: Use individual commands to get started!",
Command::descriptions()
);
bot.send_message(msg.chat.id, help_message).await?;
Ok(())
}

14
src/commands/mod.rs Normal file
View File

@@ -0,0 +1,14 @@
pub mod help;
pub mod myauctions;
pub mod mybids;
pub mod newauction;
pub mod settings;
pub mod start;
// Re-export all command handlers for easy access
pub use help::handle_help;
pub use myauctions::handle_my_auctions;
pub use mybids::handle_my_bids;
pub use newauction::handle_new_auction;
pub use settings::handle_settings;
pub use start::handle_start;

View File

@@ -0,0 +1,21 @@
use log::info;
use teloxide::{prelude::*, types::Message, Bot};
pub async fn handle_my_auctions(bot: Bot, msg: Message) -> ResponseResult<()> {
let response = "📊 My Auctions (Coming Soon)\n\n\
Here you'll be able to view and manage:\n\
• Your active auctions\n\
• Auction performance\n\
• Bid history\n\
• Winner selection (for blind auctions)\n\n\
Feature in development! 🔧";
info!(
"User {} ({}) checked their auctions",
msg.chat.username().unwrap_or("unknown"),
msg.chat.id
);
bot.send_message(msg.chat.id, response).await?;
Ok(())
}

21
src/commands/mybids.rs Normal file
View File

@@ -0,0 +1,21 @@
use log::info;
use teloxide::{prelude::*, types::Message, Bot};
pub async fn handle_my_bids(bot: Bot, msg: Message) -> ResponseResult<()> {
let response = "🎯 My Bids (Coming Soon)\n\n\
Here you'll be able to view:\n\
• Your active bids\n\
• Bid status (winning/outbid)\n\
• Proxy bid settings\n\
• Auction end times\n\n\
Feature in development! 🏗️";
info!(
"User {} ({}) checked their bids",
msg.chat.username().unwrap_or("unknown"),
msg.chat.id
);
bot.send_message(msg.chat.id, response).await?;
Ok(())
}

View File

@@ -0,0 +1,21 @@
use log::info;
use teloxide::{prelude::*, types::Message, Bot};
pub async fn handle_new_auction(bot: Bot, msg: Message) -> ResponseResult<()> {
let response = "🏗️ New Auction Creation (Coming Soon)\n\n\
This feature will allow you to create:\n\
• Standard time-based auctions\n\
• Multi-slot auctions\n\
• Fixed price sales\n\
• Blind auctions\n\n\
Stay tuned! 🎪";
info!(
"User {} ({}) attempted to create new auction",
msg.chat.username().unwrap_or("unknown"),
msg.chat.id
);
bot.send_message(msg.chat.id, response).await?;
Ok(())
}

21
src/commands/settings.rs Normal file
View File

@@ -0,0 +1,21 @@
use log::info;
use teloxide::{prelude::*, types::Message, Bot};
pub async fn handle_settings(bot: Bot, msg: Message) -> ResponseResult<()> {
let response = "⚙️ Settings (Coming Soon)\n\n\
Here you'll be able to configure:\n\
• Notification preferences\n\
• Language settings\n\
• Default bid increments\n\
• Outbid alerts\n\n\
Feature in development! 🛠️";
info!(
"User {} ({}) accessed settings",
msg.chat.username().unwrap_or("unknown"),
msg.chat.id
);
bot.send_message(msg.chat.id, response).await?;
Ok(())
}

22
src/commands/start.rs Normal file
View File

@@ -0,0 +1,22 @@
use log::info;
use teloxide::{prelude::*, types::Message, Bot};
pub async fn handle_start(bot: Bot, msg: Message) -> ResponseResult<()> {
let welcome_message = "🎯 Welcome to Pawctioneer Bot! 🎯\n\n\
This bot helps you participate in various types of auctions:\n\
• Standard auctions with anti-sniping protection\n\
• Multi-slot auctions (multiple winners)\n\
• Fixed price sales\n\
• Blind auctions\n\n\
Use /help to see all available commands.\n\n\
Ready to start your auction experience? 🚀";
info!(
"User {} ({}) started the bot",
msg.chat.username().unwrap_or("unknown"),
msg.chat.id
);
bot.send_message(msg.chat.id, welcome_message).await?;
Ok(())
}

84
src/config.rs Normal file
View File

@@ -0,0 +1,84 @@
use anyhow::{Context, Result};
use std::env;
#[derive(Debug, Clone)]
pub struct Config {
/// Telegram bot token (required)
pub telegram_token: String,
/// Database URL (required)
pub database_url: String,
/// Admin user ID for administrative commands
pub admin_user_id: Option<i64>,
/// Port for the web interface (future feature)
pub web_port: u16,
}
impl Config {
/// Load and validate configuration from environment variables
///
/// This function expects a .env file to be present or environment variables to be set.
/// Required variables: TELOXIDE_TOKEN
/// Optional variables: DATABASE_URL, ADMIN_USER_ID, WEB_PORT
///
/// The configuration is automatically validated during construction.
pub fn from_env() -> Result<Self> {
// Load .env file if present (fails silently if not found)
let _ = dotenvy::dotenv();
let telegram_token = env::var("TELOXIDE_TOKEN")
.context("TELOXIDE_TOKEN environment variable is required")?;
let database_url =
env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:pawctioneer_bot.db".to_string());
let admin_user_id = env::var("ADMIN_USER_ID")
.ok()
.and_then(|s| s.parse::<i64>().ok());
let web_port = env::var("WEB_PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse::<u16>()
.context("WEB_PORT must be a valid port number")?;
let config = Config {
telegram_token,
database_url,
admin_user_id,
web_port,
};
// Automatically validate before returning
config.validate()?;
Ok(config)
}
/// Internal validation method called automatically by from_env()
///
/// This method validates the configuration and logs important settings.
/// It's called internally and doesn't need to be called manually.
fn validate(&self) -> Result<()> {
if self.telegram_token.is_empty() {
anyhow::bail!("Telegram token cannot be empty");
}
if self.database_url.is_empty() {
anyhow::bail!("Database URL cannot be empty");
}
// Log configuration (without sensitive data)
log::info!("Configuration loaded:");
log::info!(" Database URL: {}", self.database_url);
log::info!(" Web Port: {}", self.web_port);
if let Some(admin_id) = self.admin_user_id {
log::info!(" Admin User ID: {}", admin_id);
} else {
log::info!(" Admin User ID: Not set");
}
log::info!(" Telegram Token: [CONFIGURED]");
Ok(())
}
}

View File

@@ -1,3 +1,63 @@
fn main() {
println!("Hello, world!");
use anyhow::Result;
use log::info;
use teloxide::{prelude::*, utils::command::BotCommands};
mod commands;
mod config;
use commands::*;
use config::Config;
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase", description = "Auction Bot Commands")]
pub enum Command {
#[command(description = "Show welcome message")]
Start,
#[command(description = "Show help message")]
Help,
#[command(description = "Create a new auction")]
NewAuction,
#[command(description = "View your auctions as a seller")]
MyAuctions,
#[command(description = "View your active bids")]
MyBids,
#[command(description = "Configure notifications")]
Settings,
}
// No longer needed - dptree will dispatch directly!
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging
env_logger::init();
// Load and validate configuration from environment/.env file
let config = Config::from_env()?;
info!("Starting Pawctioneer Bot...");
let bot = Bot::new(&config.telegram_token);
// Create dispatcher with direct command routing
Dispatcher::builder(
bot,
Update::filter_message().branch(
dptree::entry()
.filter_command::<Command>()
.branch(dptree::case![Command::Start].endpoint(handle_start))
.branch(dptree::case![Command::Help].endpoint(handle_help))
.branch(dptree::case![Command::NewAuction].endpoint(handle_new_auction))
.branch(dptree::case![Command::MyAuctions].endpoint(handle_my_auctions))
.branch(dptree::case![Command::MyBids].endpoint(handle_my_bids))
.branch(dptree::case![Command::Settings].endpoint(handle_settings)),
),
)
.dependencies(dptree::deps![])
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
Ok(())
}