...
This commit is contained in:
		
							
								
								
									
										29
									
								
								herodb/src/db/macros.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								herodb/src/db/macros.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
/// Macro to implement typed access methods on the DB struct for a given model
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! impl_model_methods {
 | 
			
		||||
    ($model:ty, $singular:ident, $plural:ident) => {
 | 
			
		||||
        impl DB {
 | 
			
		||||
            paste::paste! {
 | 
			
		||||
                /// Insert a model instance into the database
 | 
			
		||||
                pub fn [<insert_ $singular>](&self, item: &$model) -> SledDBResult<()> {
 | 
			
		||||
                    self.set(item)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// Get a model instance by its ID
 | 
			
		||||
                pub fn [<get_ $singular>](&self, id: u32) -> SledDBResult<$model> {
 | 
			
		||||
                    self.get::<$model>(&id.to_string())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// Delete a model instance by its ID
 | 
			
		||||
                pub fn [<delete_ $singular>](&self, id: u32) -> SledDBResult<()> {
 | 
			
		||||
                    self.delete::<$model>(&id.to_string())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// List all model instances
 | 
			
		||||
                pub fn [<list_ $plural>](&self) -> SledDBResult<Vec<$model>> {
 | 
			
		||||
                    self.list::<$model>()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
pub mod base;
 | 
			
		||||
pub mod db;
 | 
			
		||||
pub mod macros;
 | 
			
		||||
pub mod model_methods;
 | 
			
		||||
 | 
			
		||||
// Re-export everything needed at the module level
 | 
			
		||||
pub use base::{SledDB, SledDBError, SledDBResult, Storable, SledModel};
 | 
			
		||||
pub use db::{DB, DBBuilder, ModelRegistration, SledModelRegistration};
 | 
			
		||||
pub use db::{DB, DBBuilder};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								herodb/src/db/model_methods.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								herodb/src/db/model_methods.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
use crate::db::db::DB;
 | 
			
		||||
use crate::db::base::{SledDBResult, SledModel};
 | 
			
		||||
use crate::impl_model_methods;
 | 
			
		||||
use crate::models::biz::product::Product;
 | 
			
		||||
use crate::models::biz::sale::Sale;
 | 
			
		||||
use crate::models::biz::Currency;
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Product
 | 
			
		||||
impl_model_methods!(Product, product, products);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Sale
 | 
			
		||||
impl_model_methods!(Sale, sale, sales);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Currency
 | 
			
		||||
impl_model_methods!(Currency, currency, currencies);
 | 
			
		||||
@@ -6,10 +6,6 @@
 | 
			
		||||
// Core modules
 | 
			
		||||
mod db;
 | 
			
		||||
mod error;
 | 
			
		||||
pub mod server;
 | 
			
		||||
 | 
			
		||||
// Domain-specific modules
 | 
			
		||||
pub mod zaz;
 | 
			
		||||
 | 
			
		||||
// Re-exports
 | 
			
		||||
pub use error::Error;
 | 
			
		||||
 
 | 
			
		||||
@@ -243,9 +243,27 @@ let mut sale = SaleBuilder::new()
 | 
			
		||||
sale.update_status(SaleStatus::Completed);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Benefits of the Builder Pattern
 | 
			
		||||
## Database Operations
 | 
			
		||||
 | 
			
		||||
The library provides model-specific convenience methods for common database operations:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Insert a product
 | 
			
		||||
db.insert_product(&product).expect("Failed to insert product");
 | 
			
		||||
 | 
			
		||||
// Retrieve a product by ID
 | 
			
		||||
let retrieved_product = db.get_product(1).expect("Failed to retrieve product");
 | 
			
		||||
 | 
			
		||||
// List all products
 | 
			
		||||
let all_products = db.list_products().expect("Failed to list products");
 | 
			
		||||
 | 
			
		||||
// Delete a product
 | 
			
		||||
db.delete_product(1).expect("Failed to delete product");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
These methods are available for all root objects:
 | 
			
		||||
 | 
			
		||||
- `insert_product`, `get_product`, `delete_product`, `list_products` for Product
 | 
			
		||||
- `insert_currency`, `get_currency`, `delete_currency`, `list_currencies` for Currency
 | 
			
		||||
- `insert_sale`, `get_sale`, `delete_sale`, `list_sales` for Sale
 | 
			
		||||
 | 
			
		||||
- You don't need to remember the order of parameters.
 | 
			
		||||
- You get readable, self-documenting code.
 | 
			
		||||
- It's easier to provide defaults or optional values.
 | 
			
		||||
- Validation happens at build time, ensuring all required fields are provided.
 | 
			
		||||
@@ -1,368 +0,0 @@
 | 
			
		||||
//! API module for the HeroDB server
 | 
			
		||||
 | 
			
		||||
use crate::core::DB;
 | 
			
		||||
use crate::server::models::{ApiError, SuccessResponse, UserCreate, UserUpdate, SaleCreate, SaleStatusUpdate,
 | 
			
		||||
    UserResponse, SuccessOrError
 | 
			
		||||
};
 | 
			
		||||
use crate::zaz::create_zaz_db;
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use crate::zaz::models::sale::{SaleStatus, SaleItem};
 | 
			
		||||
use crate::zaz::models::product::Currency;
 | 
			
		||||
use poem_openapi::{
 | 
			
		||||
    param::Path,
 | 
			
		||||
    payload::Json,
 | 
			
		||||
    OpenApi,
 | 
			
		||||
};
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
 | 
			
		||||
/// API handler struct that holds the database connection
 | 
			
		||||
pub struct Api {
 | 
			
		||||
    db: Arc<Mutex<DB>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Api {
 | 
			
		||||
    /// Create a new API instance with the given database path
 | 
			
		||||
    pub fn new(db_path: PathBuf) -> Self {
 | 
			
		||||
        // Create the DB
 | 
			
		||||
        let db = match create_zaz_db(db_path) {
 | 
			
		||||
            Ok(db) => db,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Failed to create DB: {}", e);
 | 
			
		||||
                panic!("Failed to initialize database");
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Wrap in Arc<Mutex> for thread safety
 | 
			
		||||
        Self {
 | 
			
		||||
            db: Arc::new(Mutex::new(db)),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OpenAPI implementation for the API
 | 
			
		||||
#[OpenApi]
 | 
			
		||||
impl Api {
 | 
			
		||||
    /// Get all users
 | 
			
		||||
    #[oai(path = "/users", method = "get")]
 | 
			
		||||
    async fn get_users(&self) -> Json<String> {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.list::<User>() {
 | 
			
		||||
            Ok(users) => {
 | 
			
		||||
                // Convert to JSON manually
 | 
			
		||||
                let json_result = serde_json::to_string(&users).unwrap_or_else(|_| "[]".to_string());
 | 
			
		||||
                Json(json_result)
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error listing users: {}", e);
 | 
			
		||||
                Json("[]".to_string())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a user by ID
 | 
			
		||||
    #[oai(path = "/users/:id", method = "get")]
 | 
			
		||||
    async fn get_user(&self, id: Path<u32>) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.get::<User>(&id.0.to_string()) {
 | 
			
		||||
            Ok(user) => {
 | 
			
		||||
                // Convert to JSON manually
 | 
			
		||||
                let json_result = serde_json::to_string(&user).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error getting user: {}", e);
 | 
			
		||||
                UserResponse::NotFound(Json(ApiError::not_found(id.0)))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new user
 | 
			
		||||
    #[oai(path = "/users", method = "post")]
 | 
			
		||||
    async fn create_user(
 | 
			
		||||
        &self,
 | 
			
		||||
        user: Json<UserCreate>,
 | 
			
		||||
    ) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        // Find the next available ID
 | 
			
		||||
        let users: Vec<User> = match db.list() {
 | 
			
		||||
            Ok(users) => users,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error listing users: {}", e);
 | 
			
		||||
                return UserResponse::InternalError(Json(ApiError::internal_error("Failed to generate ID")));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let next_id = users.iter().map(|u| u.id).max().unwrap_or(0) + 1;
 | 
			
		||||
        
 | 
			
		||||
        // Create the new user
 | 
			
		||||
        let new_user = User::new(
 | 
			
		||||
            next_id,
 | 
			
		||||
            user.name.clone(),
 | 
			
		||||
            user.email.clone(),
 | 
			
		||||
            user.password.clone(),
 | 
			
		||||
            user.company.clone(),
 | 
			
		||||
            user.role.clone(),
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Save the user
 | 
			
		||||
        match db.set(&new_user) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&new_user).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            },
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error creating user: {}", e);
 | 
			
		||||
                UserResponse::InternalError(Json(ApiError::internal_error("Failed to create user")))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update a user
 | 
			
		||||
    #[oai(path = "/users/:id", method = "put")]
 | 
			
		||||
    async fn update_user(
 | 
			
		||||
        &self,
 | 
			
		||||
        id: Path<u32>,
 | 
			
		||||
        user: Json<UserUpdate>,
 | 
			
		||||
    ) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        // Get the existing user
 | 
			
		||||
        let existing_user: User = match db.get(&id.0.to_string()) {
 | 
			
		||||
            Ok(user) => user,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error getting user: {}", e);
 | 
			
		||||
                return UserResponse::NotFound(Json(ApiError::not_found(id.0)));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Update the user
 | 
			
		||||
        let updated_user = User {
 | 
			
		||||
            id: existing_user.id,
 | 
			
		||||
            name: user.name.clone().unwrap_or(existing_user.name),
 | 
			
		||||
            email: user.email.clone().unwrap_or(existing_user.email),
 | 
			
		||||
            password: user.password.clone().unwrap_or(existing_user.password),
 | 
			
		||||
            company: user.company.clone().unwrap_or(existing_user.company),
 | 
			
		||||
            role: user.role.clone().unwrap_or(existing_user.role),
 | 
			
		||||
            created_at: existing_user.created_at,
 | 
			
		||||
            updated_at: Utc::now(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Save the updated user
 | 
			
		||||
        match db.set(&updated_user) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&updated_user).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            },
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error updating user: {}", e);
 | 
			
		||||
                UserResponse::InternalError(Json(ApiError::internal_error("Failed to update user")))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete a user
 | 
			
		||||
    #[oai(path = "/users/:id", method = "delete")]
 | 
			
		||||
    async fn delete_user(&self, id: Path<u32>) -> SuccessOrError {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        match db.delete::<User>(&id.0.to_string()) {
 | 
			
		||||
            Ok(_) => SuccessOrError::Ok(Json(SuccessResponse {
 | 
			
		||||
                success: true,
 | 
			
		||||
                message: format!("User with ID {} deleted", id.0),
 | 
			
		||||
            })),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error deleting user: {}", e);
 | 
			
		||||
                SuccessOrError::NotFound(Json(ApiError::not_found(id.0)))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get all products
 | 
			
		||||
    #[oai(path = "/products", method = "get")]
 | 
			
		||||
    async fn get_products(&self) -> Json<String> {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.list::<Product>() {
 | 
			
		||||
            Ok(products) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&products).unwrap_or_else(|_| "[]".to_string());
 | 
			
		||||
                Json(json_result)
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error listing products: {}", e);
 | 
			
		||||
                Json("[]".to_string())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a product by ID
 | 
			
		||||
    #[oai(path = "/products/:id", method = "get")]
 | 
			
		||||
    async fn get_product(&self, id: Path<u32>) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.get::<Product>(&id.0.to_string()) {
 | 
			
		||||
            Ok(product) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&product).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error getting product: {}", e);
 | 
			
		||||
                UserResponse::NotFound(Json(ApiError::not_found(id.0)))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get all sales
 | 
			
		||||
    #[oai(path = "/sales", method = "get")]
 | 
			
		||||
    async fn get_sales(&self) -> Json<String> {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.list::<Sale>() {
 | 
			
		||||
            Ok(sales) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&sales).unwrap_or_else(|_| "[]".to_string());
 | 
			
		||||
                Json(json_result)
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error listing sales: {}", e);
 | 
			
		||||
                Json("[]".to_string())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a sale by ID
 | 
			
		||||
    #[oai(path = "/sales/:id", method = "get")]
 | 
			
		||||
    async fn get_sale(&self, id: Path<u32>) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        match db.get::<Sale>(&id.0.to_string()) {
 | 
			
		||||
            Ok(sale) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&sale).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error getting sale: {}", e);
 | 
			
		||||
                UserResponse::NotFound(Json(ApiError::not_found(id.0)))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new sale
 | 
			
		||||
    #[oai(path = "/sales", method = "post")]
 | 
			
		||||
    async fn create_sale(
 | 
			
		||||
        &self,
 | 
			
		||||
        sale: Json<SaleCreate>,
 | 
			
		||||
    ) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        // Find the next available ID
 | 
			
		||||
        let sales: Vec<Sale> = match db.list() {
 | 
			
		||||
            Ok(sales) => sales,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error listing sales: {}", e);
 | 
			
		||||
                return UserResponse::InternalError(Json(ApiError::internal_error("Failed to generate ID")));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let next_id = sales.iter().map(|s| s.id).max().unwrap_or(0) + 1;
 | 
			
		||||
        
 | 
			
		||||
        // Create the new sale
 | 
			
		||||
        let mut new_sale = Sale::new(
 | 
			
		||||
            next_id,
 | 
			
		||||
            sale.company_id,
 | 
			
		||||
            sale.buyer_name.clone(),
 | 
			
		||||
            sale.buyer_email.clone(),
 | 
			
		||||
            sale.currency_code.clone(),
 | 
			
		||||
            SaleStatus::Pending,
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Add items if provided
 | 
			
		||||
        if let Some(items) = &sale.items {
 | 
			
		||||
            for (i, item) in items.iter().enumerate() {
 | 
			
		||||
                let item_id = (i + 1) as u32;
 | 
			
		||||
                let active_till = Utc::now() + chrono::Duration::days(365); // Default 1 year
 | 
			
		||||
                
 | 
			
		||||
                let sale_item = SaleItem::new(
 | 
			
		||||
                    item_id,
 | 
			
		||||
                    next_id,
 | 
			
		||||
                    item.product_id,
 | 
			
		||||
                    item.name.clone(),
 | 
			
		||||
                    item.quantity,
 | 
			
		||||
                    Currency {
 | 
			
		||||
                        amount: item.unit_price,
 | 
			
		||||
                        currency_code: sale.currency_code.clone(),
 | 
			
		||||
                    },
 | 
			
		||||
                    active_till,
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                new_sale.add_item(sale_item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Save the sale
 | 
			
		||||
        match db.set(&new_sale) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&new_sale).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error creating sale: {}", e);
 | 
			
		||||
                UserResponse::InternalError(Json(ApiError::internal_error("Failed to create sale")))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update a sale status
 | 
			
		||||
    #[oai(path = "/sales/:id/status", method = "put")]
 | 
			
		||||
    async fn update_sale_status(
 | 
			
		||||
        &self,
 | 
			
		||||
        id: Path<u32>,
 | 
			
		||||
        status: Json<SaleStatusUpdate>,
 | 
			
		||||
    ) -> UserResponse {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        // Get the existing sale
 | 
			
		||||
        let mut existing_sale: Sale = match db.get(&id.0.to_string()) {
 | 
			
		||||
            Ok(sale) => sale,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error getting sale: {}", e);
 | 
			
		||||
                return UserResponse::NotFound(Json(ApiError::not_found(id.0)));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Parse and update the status
 | 
			
		||||
        let new_status = match status.parse_status() {
 | 
			
		||||
            Ok(status) => status,
 | 
			
		||||
            Err(e) => return UserResponse::InternalError(Json(e)),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Update the status
 | 
			
		||||
        existing_sale.update_status(new_status);
 | 
			
		||||
        
 | 
			
		||||
        // Save the updated sale
 | 
			
		||||
        match db.set(&existing_sale) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                let json_result = serde_json::to_string(&existing_sale).unwrap_or_else(|_| "{}".to_string());
 | 
			
		||||
                UserResponse::Ok(Json(json_result))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error updating sale: {}", e);
 | 
			
		||||
                UserResponse::InternalError(Json(ApiError::internal_error("Failed to update sale")))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete a sale
 | 
			
		||||
    #[oai(path = "/sales/:id", method = "delete")]
 | 
			
		||||
    async fn delete_sale(&self, id: Path<u32>) -> SuccessOrError {
 | 
			
		||||
        let db = self.db.lock().unwrap();
 | 
			
		||||
        
 | 
			
		||||
        match db.delete::<Sale>(&id.0.to_string()) {
 | 
			
		||||
            Ok(_) => SuccessOrError::Ok(Json(SuccessResponse {
 | 
			
		||||
                success: true,
 | 
			
		||||
                message: format!("Sale with ID {} deleted", id.0),
 | 
			
		||||
            })),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error deleting sale: {}", e);
 | 
			
		||||
                SuccessOrError::NotFound(Json(ApiError::not_found(id.0)))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,289 +0,0 @@
 | 
			
		||||
//! Extensions to make the zaz models compatible with OpenAPI
 | 
			
		||||
//!
 | 
			
		||||
//! This module adds the necessary traits and implementations to make the
 | 
			
		||||
//! existing zaz models work with OpenAPI.
 | 
			
		||||
 | 
			
		||||
use poem_openapi::types::{ToSchema, Type};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
 | 
			
		||||
// Make DateTime<Utc> compatible with OpenAPI
 | 
			
		||||
impl Type for DateTime<Utc> {
 | 
			
		||||
    const IS_REQUIRED: bool = true;
 | 
			
		||||
    
 | 
			
		||||
    type RawValueType = String;
 | 
			
		||||
    
 | 
			
		||||
    type RawElementValueType = Self::RawValueType;
 | 
			
		||||
    
 | 
			
		||||
    fn name() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "DateTime".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn schema_ref() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "string".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn as_raw_value(&self) -> Option<Self::RawValueType> {
 | 
			
		||||
        Some(self.to_rfc3339())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn raw_element_iter<'a>(
 | 
			
		||||
        &'a self,
 | 
			
		||||
    ) -> Box<dyn Iterator<Item = Self::RawElementValueType> + 'a> {
 | 
			
		||||
        Box::new(self.as_raw_value().into_iter())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make Currency compatible with OpenAPI
 | 
			
		||||
impl Type for Currency {
 | 
			
		||||
    const IS_REQUIRED: bool = true;
 | 
			
		||||
    
 | 
			
		||||
    type RawValueType = serde_json::Value;
 | 
			
		||||
    
 | 
			
		||||
    type RawElementValueType = Self::RawValueType;
 | 
			
		||||
    
 | 
			
		||||
    fn name() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "Currency".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn schema_ref() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "object".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn as_raw_value(&self) -> Option<Self::RawValueType> {
 | 
			
		||||
        Some(serde_json::json!({
 | 
			
		||||
            "amount": self.amount,
 | 
			
		||||
            "currency_code": self.currency_code
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn raw_element_iter<'a>(
 | 
			
		||||
        &'a self,
 | 
			
		||||
    ) -> Box<dyn Iterator<Item = Self::RawElementValueType> + 'a> {
 | 
			
		||||
        Box::new(self.as_raw_value().into_iter())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make SaleStatus compatible with OpenAPI
 | 
			
		||||
impl Type for SaleStatus {
 | 
			
		||||
    const IS_REQUIRED: bool = true;
 | 
			
		||||
    
 | 
			
		||||
    type RawValueType = String;
 | 
			
		||||
    
 | 
			
		||||
    type RawElementValueType = Self::RawValueType;
 | 
			
		||||
    
 | 
			
		||||
    fn name() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "SaleStatus".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn schema_ref() -> std::borrow::Cow<'static, str> {
 | 
			
		||||
        "string".into()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn as_raw_value(&self) -> Option<Self::RawValueType> {
 | 
			
		||||
        Some(match self {
 | 
			
		||||
            SaleStatus::Pending => "pending".to_string(),
 | 
			
		||||
            SaleStatus::Completed => "completed".to_string(),
 | 
			
		||||
            SaleStatus::Cancelled => "cancelled".to_string(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn raw_element_iter<'a>(
 | 
			
		||||
        &'a self,
 | 
			
		||||
    ) -> Box<dyn Iterator<Item = Self::RawElementValueType> + 'a> {
 | 
			
		||||
        Box::new(self.as_raw_value().into_iter())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Schema generation for User
 | 
			
		||||
impl ToSchema for User {
 | 
			
		||||
    fn schema() -> poem_openapi::registry::MetaSchema {
 | 
			
		||||
        let mut schema = poem_openapi::registry::MetaSchema::new("User");
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "name".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "email".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "company".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "role".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "created_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "updated_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        // Note: We exclude password for security reasons
 | 
			
		||||
        schema
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Schema generation for Product
 | 
			
		||||
impl ToSchema for Product {
 | 
			
		||||
    fn schema() -> poem_openapi::registry::MetaSchema {
 | 
			
		||||
        let mut schema = poem_openapi::registry::MetaSchema::new("Product");
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "company_id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "name".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "description".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "price".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <Currency as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "status".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "created_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "updated_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Schema generation for SaleItem
 | 
			
		||||
impl ToSchema for SaleItem {
 | 
			
		||||
    fn schema() -> poem_openapi::registry::MetaSchema {
 | 
			
		||||
        let mut schema = poem_openapi::registry::MetaSchema::new("SaleItem");
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "sale_id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "product_id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "name".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "quantity".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(i32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "unit_price".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <Currency as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "subtotal".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <Currency as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "active_till".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Schema generation for Sale
 | 
			
		||||
impl ToSchema for Sale {
 | 
			
		||||
    fn schema() -> poem_openapi::registry::MetaSchema {
 | 
			
		||||
        let mut schema = poem_openapi::registry::MetaSchema::new("Sale");
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "company_id".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(u32::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "buyer_name".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "buyer_email".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(String::schema())),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "total_amount".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <Currency as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "status".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <SaleStatus as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "sale_date".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "created_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "updated_at".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <DateTime<Utc> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema.properties.insert(
 | 
			
		||||
            "items".to_string(),
 | 
			
		||||
            poem_openapi::registry::MetaSchemaRef::Inline(Box::new(
 | 
			
		||||
                <Vec<SaleItem> as ToSchema>::schema(),
 | 
			
		||||
            )),
 | 
			
		||||
        );
 | 
			
		||||
        schema
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
You're a Rust developer assistant.
 | 
			
		||||
 | 
			
		||||
Please generate a complete Rust web server using `poem` and `poem-openapi`. It should:
 | 
			
		||||
 | 
			
		||||
1. Create a basic `#[OpenApi]` interface.
 | 
			
		||||
2. Automatically generate and serve an OpenAPI 3.0 spec at `/api/openapi.json`.
 | 
			
		||||
3. Serve a Swagger UI interface at `/docs`.
 | 
			
		||||
4. Expose a function I define in another module (e.g., `my_logic::do_something()`).
 | 
			
		||||
5. The endpoint should be `/do` (HTTP GET) and return a JSON response.
 | 
			
		||||
 | 
			
		||||
Use these crates:
 | 
			
		||||
- `poem`
 | 
			
		||||
- `poem-openapi`
 | 
			
		||||
- `tokio`
 | 
			
		||||
- `uuid` (optional, if needed)
 | 
			
		||||
 | 
			
		||||
We want to create crud and also other methods of herodb/src/zaz/models
 | 
			
		||||
 | 
			
		||||
To see how we call this model logic see herodb/src/zaz/cmd/examples.rs
 | 
			
		||||
 | 
			
		||||
And use it inside the API handler.
 | 
			
		||||
 | 
			
		||||
The project should have:
 | 
			
		||||
- `main.rs` (entry point)
 | 
			
		||||
- `sale.rs` (logic per file e.g. sale, product, ...)
 | 
			
		||||
- Clear type annotations and minimal boilerplate.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 🧪 Example Output from AI Based on That Prompt
 | 
			
		||||
 | 
			
		||||
### `Cargo.toml`
 | 
			
		||||
 | 
			
		||||
```toml
 | 
			
		||||
[package]
 | 
			
		||||
name = "poem-api-server"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
poem = "1"
 | 
			
		||||
poem-openapi = "3"
 | 
			
		||||
tokio = { version = "1", features = ["full"] }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### `src/main.rs`
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use poem::{listener::TcpListener, Route, Server};
 | 
			
		||||
use poem_openapi::{payload::Json, OpenApi, OpenApiService};
 | 
			
		||||
 | 
			
		||||
mod my_logic;
 | 
			
		||||
 | 
			
		||||
struct Api;
 | 
			
		||||
 | 
			
		||||
#[OpenApi]
 | 
			
		||||
impl Api {
 | 
			
		||||
    #[oai(path = "/do", method = "get")]
 | 
			
		||||
    async fn do_action(&self) -> Json<String> {
 | 
			
		||||
        Json(my_logic::do_something())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() {
 | 
			
		||||
    let api_service =
 | 
			
		||||
        OpenApiService::new(Api, "My API", "1.0").server("http://localhost:3000/api");
 | 
			
		||||
 | 
			
		||||
    let ui = api_service.swagger_ui();
 | 
			
		||||
 | 
			
		||||
    let app = Route::new()
 | 
			
		||||
        .nest("/api", api_service)
 | 
			
		||||
        .nest("/docs", ui);
 | 
			
		||||
 | 
			
		||||
    Server::new(TcpListener::bind("127.0.0.1:3000"))
 | 
			
		||||
        .run(app)
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### ✅ Result
 | 
			
		||||
 | 
			
		||||
- Open `/api/do` → Calls your logic and returns a JSON response.
 | 
			
		||||
- Open `/docs` → Interactive Swagger UI
 | 
			
		||||
- Open `/api/openapi.json` → Full OpenAPI spec
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
//! Server module for the HeroDB API
 | 
			
		||||
//! 
 | 
			
		||||
//! This module provides a web API server using Poem and OpenAPI.
 | 
			
		||||
 | 
			
		||||
pub mod api;
 | 
			
		||||
pub mod models;
 | 
			
		||||
 | 
			
		||||
use poem::{
 | 
			
		||||
    listener::TcpListener, 
 | 
			
		||||
    Route,
 | 
			
		||||
    Server
 | 
			
		||||
};
 | 
			
		||||
use poem_openapi::OpenApiService;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
/// Start the API server
 | 
			
		||||
pub async fn start_server(db_path: PathBuf, host: &str, port: u16) -> Result<(), std::io::Error> {
 | 
			
		||||
    // Create the API service
 | 
			
		||||
    let api_service = OpenApiService::new(
 | 
			
		||||
        api::Api::new(db_path),
 | 
			
		||||
        "HeroDB API",
 | 
			
		||||
        env!("CARGO_PKG_VERSION"),
 | 
			
		||||
    )
 | 
			
		||||
    .server(format!("http://{}:{}/api", host, port));
 | 
			
		||||
 | 
			
		||||
    // Create Swagger UI
 | 
			
		||||
    let swagger_ui = api_service.swagger_ui();
 | 
			
		||||
 | 
			
		||||
    // Create the main route
 | 
			
		||||
    let app = Route::new()
 | 
			
		||||
        .nest("/api", api_service)
 | 
			
		||||
        .nest("/swagger", swagger_ui);
 | 
			
		||||
 | 
			
		||||
    // Start the server
 | 
			
		||||
    println!("Starting server on {}:{}", host, port);
 | 
			
		||||
    println!("API Documentation: http://{}:{}/swagger", host, port);
 | 
			
		||||
    
 | 
			
		||||
    Server::new(TcpListener::bind(format!("{}:{}", host, port)))
 | 
			
		||||
        .run(app)
 | 
			
		||||
        .await
 | 
			
		||||
}
 | 
			
		||||
@@ -1,145 +0,0 @@
 | 
			
		||||
//! API models for the HeroDB server
 | 
			
		||||
 | 
			
		||||
use crate::zaz::models::sale::SaleStatus;
 | 
			
		||||
use poem_openapi::{Object, ApiResponse};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
/// API error response
 | 
			
		||||
#[derive(Debug, Object)]
 | 
			
		||||
pub struct ApiError {
 | 
			
		||||
    /// Error code
 | 
			
		||||
    pub code: u16,
 | 
			
		||||
    /// Error message
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ApiError {
 | 
			
		||||
    pub fn not_found(id: impl ToString) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            code: 404,
 | 
			
		||||
            message: format!("Resource with ID {} not found", id.to_string()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn internal_error(msg: impl ToString) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            code: 500,
 | 
			
		||||
            message: msg.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// API success response
 | 
			
		||||
#[derive(Debug, Object)]
 | 
			
		||||
pub struct SuccessResponse {
 | 
			
		||||
    /// Success flag
 | 
			
		||||
    pub success: bool,
 | 
			
		||||
    /// Success message
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// User create request
 | 
			
		||||
#[derive(Debug, Object)]
 | 
			
		||||
pub struct UserCreate {
 | 
			
		||||
    /// User name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// User email
 | 
			
		||||
    pub email: String,
 | 
			
		||||
    /// User password
 | 
			
		||||
    pub password: String,
 | 
			
		||||
    /// User company
 | 
			
		||||
    pub company: String,
 | 
			
		||||
    /// User role
 | 
			
		||||
    pub role: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// User update request
 | 
			
		||||
#[derive(Debug, Object)]
 | 
			
		||||
pub struct UserUpdate {
 | 
			
		||||
    /// User name
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub name: Option<String>,
 | 
			
		||||
    /// User email
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub email: Option<String>,
 | 
			
		||||
    /// User password
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    /// User company
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub company: Option<String>,
 | 
			
		||||
    /// User role
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub role: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sale item create request
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Object)]
 | 
			
		||||
pub struct SaleItemCreate {
 | 
			
		||||
    /// Product ID
 | 
			
		||||
    pub product_id: u32,
 | 
			
		||||
    /// Item name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Quantity
 | 
			
		||||
    pub quantity: i32,
 | 
			
		||||
    /// Unit price
 | 
			
		||||
    pub unit_price: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sale create request
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Object)]
 | 
			
		||||
pub struct SaleCreate {
 | 
			
		||||
    /// Company ID
 | 
			
		||||
    pub company_id: u32,
 | 
			
		||||
    /// Buyer name
 | 
			
		||||
    pub buyer_name: String,
 | 
			
		||||
    /// Buyer email
 | 
			
		||||
    pub buyer_email: String,
 | 
			
		||||
    /// Currency code
 | 
			
		||||
    pub currency_code: String,
 | 
			
		||||
    /// Items
 | 
			
		||||
    #[oai(skip_serializing_if_is_none)]
 | 
			
		||||
    pub items: Option<Vec<SaleItemCreate>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sale status update request
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Object)]
 | 
			
		||||
pub struct SaleStatusUpdate {
 | 
			
		||||
    /// New status
 | 
			
		||||
    pub status: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SaleStatusUpdate {
 | 
			
		||||
    pub fn parse_status(&self) -> Result<SaleStatus, ApiError> {
 | 
			
		||||
        match self.status.to_lowercase().as_str() {
 | 
			
		||||
            "pending" => Ok(SaleStatus::Pending),
 | 
			
		||||
            "completed" => Ok(SaleStatus::Completed),
 | 
			
		||||
            "cancelled" => Ok(SaleStatus::Cancelled),
 | 
			
		||||
            _ => Err(ApiError {
 | 
			
		||||
                code: 400,
 | 
			
		||||
                message: format!("Invalid status: {}", self.status),
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Define API responses
 | 
			
		||||
#[derive(Debug, ApiResponse)]
 | 
			
		||||
pub enum UserResponse {
 | 
			
		||||
    #[oai(status = 200)]
 | 
			
		||||
    Ok(poem_openapi::payload::Json<String>),
 | 
			
		||||
    #[oai(status = 404)]
 | 
			
		||||
    NotFound(poem_openapi::payload::Json<ApiError>),
 | 
			
		||||
    #[oai(status = 500)]
 | 
			
		||||
    InternalError(poem_openapi::payload::Json<ApiError>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, ApiResponse)]
 | 
			
		||||
pub enum SuccessOrError {
 | 
			
		||||
    #[oai(status = 200)]
 | 
			
		||||
    Ok(poem_openapi::payload::Json<SuccessResponse>),
 | 
			
		||||
    #[oai(status = 404)]
 | 
			
		||||
    NotFound(poem_openapi::payload::Json<ApiError>),
 | 
			
		||||
    #[oai(status = 500)]
 | 
			
		||||
    InternalError(poem_openapi::payload::Json<ApiError>),
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user