added listings

This commit is contained in:
2025-12-26 20:01:08 -05:00
parent 5d0a9bb255
commit 56ebb8eba2
19 changed files with 843 additions and 130 deletions

240
src/listing/data.rs Normal file
View File

@@ -0,0 +1,240 @@
use axum::{
extract::{Path, State, Extension},
http::StatusCode,
Json,
};
use crate::auth::structs::{AppState, User};
use crate::company::structs::Company;
use crate::listing::structs::{Listing, CreateListingRequest, UpdateListingRequest};
use serde_json::json;
pub async fn get_listings(
State(app_state): State<AppState>,
Extension(user): Extension<User>,
) -> Result<Json<Vec<Listing>>, (StatusCode, Json<serde_json::Value>)> {
match sqlx::query_as::<_, Listing>(
"SELECT id, company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id, last_edited FROM listings WHERE user_id = $1 ORDER BY id DESC"
)
.bind(user.id)
.fetch_all(&*app_state.db)
.await
{
Ok(listings) => Ok(Json(listings)),
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to fetch listings: {}", e)}))
)),
}
}
pub async fn get_listing_by_id(
State(app_state): State<AppState>,
Path(listing_id): Path<i32>,
Extension(user): Extension<User>,
) -> Result<Json<Listing>, (StatusCode, Json<serde_json::Value>)> {
match sqlx::query_as::<_, Listing>(
"SELECT id, company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id, last_edited FROM listings WHERE id = $1 AND user_id = $2"
)
.bind(listing_id)
.bind(user.id)
.fetch_optional(&*app_state.db)
.await
{
Ok(Some(listing)) => Ok(Json(listing)),
Ok(None) => Err((
StatusCode::NOT_FOUND,
Json(json!({"error": "Listing not found"}))
)),
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to fetch listing: {}", e)}))
)),
}
}
pub async fn create_listing(
State(app_state): State<AppState>,
Extension(user): Extension<User>,
Json(payload): Json<CreateListingRequest>,
) -> Result<Json<Listing>, (StatusCode, Json<serde_json::Value>)> {
eprintln!("DEBUG: Starting create_listing for user_id: {}", user.id);
eprintln!("DEBUG: Payload: {:?}", payload);
// Create the listing directly without company validation
match sqlx::query_as::<_, Listing>(
"INSERT INTO listings (company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id, last_edited"
)
.bind(&payload.company_name)
.bind(payload.is_active)
.bind(payload.price_per_gallon)
.bind(payload.price_per_gallon_cash)
.bind(&payload.note)
.bind(payload.minimum_order)
.bind(payload.service)
.bind(payload.bio_percent)
.bind(&payload.phone)
.bind(&payload.online_ordering)
.bind(payload.county_id)
.bind(user.id)
.fetch_one(&*app_state.db)
.await
{
Ok(listing) => {
eprintln!("DEBUG: Successfully created listing: {:?}", listing);
Ok(Json(listing))
},
Err(e) => {
eprintln!("DEBUG: Error creating listing: {:?}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to create listing: {}", e)}))
))
},
}
}
pub async fn update_listing(
State(app_state): State<AppState>,
Path(listing_id): Path<i32>,
Extension(user): Extension<User>,
Json(payload): Json<UpdateListingRequest>,
) -> Result<Json<Listing>, (StatusCode, Json<serde_json::Value>)> {
// Build dynamic update query
let mut query = "UPDATE listings SET ".to_string();
let mut params: Vec<String> = Vec::new();
let mut param_count = 1;
if let Some(company_name) = &payload.company_name {
params.push(format!("company_name = ${}", param_count));
param_count += 1;
}
if let Some(is_active) = payload.is_active {
params.push(format!("is_active = ${}", param_count));
param_count += 1;
}
if let Some(price_per_gallon) = payload.price_per_gallon {
params.push(format!("price_per_gallon = ${}", param_count));
param_count += 1;
}
if let Some(price_per_gallon_cash) = payload.price_per_gallon_cash {
params.push(format!("price_per_gallon_cash = ${}", param_count));
param_count += 1;
}
if let Some(note) = &payload.note {
params.push(format!("note = ${}", param_count));
param_count += 1;
}
if let Some(minimum_order) = payload.minimum_order {
params.push(format!("minimum_order = ${}", param_count));
param_count += 1;
}
if let Some(service) = payload.service {
params.push(format!("service = ${}", param_count));
param_count += 1;
}
if let Some(bio_percent) = payload.bio_percent {
params.push(format!("bio_percent = ${}", param_count));
param_count += 1;
}
if let Some(phone) = &payload.phone {
params.push(format!("phone = ${}", param_count));
param_count += 1;
}
if let Some(online_ordering) = &payload.online_ordering {
params.push(format!("online_ordering = ${}", param_count));
param_count += 1;
}
if let Some(county_id) = payload.county_id {
params.push(format!("county_id = ${}", param_count));
param_count += 1;
}
if params.is_empty() {
return Err((
StatusCode::BAD_REQUEST,
Json(json!({"error": "No fields to update"}))
));
}
query.push_str(&params.join(", "));
query.push_str(&format!(" WHERE id = ${} AND user_id = ${} RETURNING *", param_count, param_count + 1));
// This is a simplified version - in production, you'd want to build the query more safely
// For now, let's use a simpler approach
match sqlx::query_as::<_, Listing>(
"UPDATE listings SET company_name = COALESCE($1, company_name), is_active = COALESCE($2, is_active), price_per_gallon = COALESCE($3, price_per_gallon), price_per_gallon_cash = COALESCE($4, price_per_gallon_cash), note = COALESCE($5, note), minimum_order = COALESCE($6, minimum_order), service = COALESCE($7, service), bio_percent = COALESCE($8, bio_percent), phone = COALESCE($9, phone), online_ordering = COALESCE($10, online_ordering), county_id = COALESCE($11, county_id), last_edited = CURRENT_TIMESTAMP WHERE id = $12 AND user_id = $13 RETURNING id, company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id, last_edited"
)
.bind(&payload.company_name)
.bind(payload.is_active)
.bind(payload.price_per_gallon)
.bind(payload.price_per_gallon_cash)
.bind(&payload.note)
.bind(payload.minimum_order)
.bind(payload.service)
.bind(payload.bio_percent)
.bind(&payload.phone)
.bind(&payload.online_ordering)
.bind(payload.county_id)
.bind(listing_id)
.bind(user.id)
.fetch_optional(&*app_state.db)
.await
{
Ok(Some(listing)) => Ok(Json(listing)),
Ok(None) => Err((
StatusCode::NOT_FOUND,
Json(json!({"error": "Listing not found"}))
)),
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to update listing: {}", e)}))
)),
}
}
pub async fn get_listings_by_county(
State(app_state): State<AppState>,
Path(county_id): Path<i32>,
) -> Result<Json<Vec<Listing>>, (StatusCode, Json<serde_json::Value>)> {
match sqlx::query_as::<_, Listing>(
"SELECT id, company_name, is_active, price_per_gallon, price_per_gallon_cash, note, minimum_order, service, bio_percent, phone, online_ordering, county_id, user_id, last_edited FROM listings WHERE county_id = $1 AND is_active = true ORDER BY last_edited DESC"
)
.bind(county_id)
.fetch_all(&*app_state.db)
.await
{
Ok(listings) => Ok(Json(listings)),
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to fetch listings: {}", e)}))
)),
}
}
pub async fn delete_listing(
State(app_state): State<AppState>,
Path(listing_id): Path<i32>,
Extension(user): Extension<User>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
match sqlx::query("DELETE FROM listings WHERE id = $1 AND user_id = $2")
.bind(listing_id)
.bind(user.id)
.execute(&*app_state.db)
.await
{
Ok(result) => {
if result.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
Json(json!({"error": "Listing not found"}))
))
} else {
Ok(Json(json!({"success": true, "message": "Listing deleted"})))
}
}
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": format!("Failed to delete listing: {}", e)}))
)),
}
}

2
src/listing/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod structs;
pub mod data;

52
src/listing/structs.rs Normal file
View File

@@ -0,0 +1,52 @@
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use chrono::{DateTime, Utc};
#[derive(Debug, Serialize, Deserialize, FromRow)]
#[allow(dead_code)]
pub struct Listing {
pub id: i32,
pub company_name: String,
pub is_active: bool,
pub price_per_gallon: f64,
pub price_per_gallon_cash: Option<f64>,
pub note: Option<String>,
pub minimum_order: Option<i32>,
pub service: bool,
pub bio_percent: i32,
pub phone: Option<String>,
pub online_ordering: String,
pub county_id: i32,
pub user_id: i32,
pub last_edited: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateListingRequest {
pub company_name: String,
pub is_active: bool,
pub price_per_gallon: f64,
pub price_per_gallon_cash: Option<f64>,
pub note: Option<String>,
pub minimum_order: Option<i32>,
pub service: bool,
pub bio_percent: i32,
pub phone: Option<String>,
pub online_ordering: String,
pub county_id: i32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateListingRequest {
pub company_name: Option<String>,
pub is_active: Option<bool>,
pub price_per_gallon: Option<f64>,
pub price_per_gallon_cash: Option<f64>,
pub note: Option<String>,
pub minimum_order: Option<i32>,
pub service: Option<bool>,
pub bio_percent: Option<i32>,
pub phone: Option<String>,
pub online_ordering: Option<String>,
pub county_id: Option<i32>,
}