...
This commit is contained in:
parent
8a5e41a265
commit
186e339740
7
herodb/Cargo.lock
generated
7
herodb/Cargo.lock
generated
@ -658,6 +658,7 @@ dependencies = [
|
|||||||
"bincode",
|
"bincode",
|
||||||
"brotli",
|
"brotli",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"paste",
|
||||||
"poem",
|
"poem",
|
||||||
"poem-openapi",
|
"poem-openapi",
|
||||||
"rhai",
|
"rhai",
|
||||||
@ -1007,6 +1008,12 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -20,6 +20,7 @@ poem = "1.3.55"
|
|||||||
poem-openapi = { version = "2.0.11", features = ["swagger-ui"] }
|
poem-openapi = { version = "2.0.11", features = ["swagger-ui"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
rhai = "1.15.1"
|
rhai = "1.15.1"
|
||||||
|
paste = "1.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "rhai_demo"
|
name = "rhai_demo"
|
||||||
|
81
herodb/README.md
Normal file
81
herodb/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# HeroDB
|
||||||
|
|
||||||
|
A database library built on top of sled with model support.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Type-safe database operations
|
||||||
|
- Builder pattern for model creation
|
||||||
|
- Transaction support
|
||||||
|
- Model-specific convenience methods
|
||||||
|
- Compression for efficient storage
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use herodb::db::{DB, DBBuilder};
|
||||||
|
use herodb::models::biz::{Product, ProductBuilder, ProductType, ProductStatus, Currency, CurrencyBuilder};
|
||||||
|
|
||||||
|
// Create a database instance
|
||||||
|
let db = DBBuilder::new("db")
|
||||||
|
.register_model::<Product>()
|
||||||
|
.register_model::<Currency>()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create database");
|
||||||
|
|
||||||
|
// Create a product using the builder pattern
|
||||||
|
let price = CurrencyBuilder::new()
|
||||||
|
.amount(29.99)
|
||||||
|
.currency_code("USD")
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build currency");
|
||||||
|
|
||||||
|
let product = ProductBuilder::new()
|
||||||
|
.id(1)
|
||||||
|
.name("Premium Service")
|
||||||
|
.description("Our premium service offering")
|
||||||
|
.price(price)
|
||||||
|
.type_(ProductType::Service)
|
||||||
|
.category("Services")
|
||||||
|
.status(ProductStatus::Available)
|
||||||
|
.max_amount(100)
|
||||||
|
.validity_days(30)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build product");
|
||||||
|
|
||||||
|
// Insert the product using the generic method
|
||||||
|
db.set(&product).expect("Failed to insert product");
|
||||||
|
|
||||||
|
// Retrieve the product
|
||||||
|
let retrieved_product = db.get::<Product>(&"1".to_string()).expect("Failed to retrieve product");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Model-Specific Convenience Methods
|
||||||
|
|
||||||
|
The library provides model-specific convenience methods for common database operations:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Insert a product using the model-specific method
|
||||||
|
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 registered models:
|
||||||
|
|
||||||
|
- `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
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
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 base;
|
||||||
pub mod db;
|
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 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
|
// Core modules
|
||||||
mod db;
|
mod db;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod server;
|
|
||||||
|
|
||||||
// Domain-specific modules
|
|
||||||
pub mod zaz;
|
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
@ -243,9 +243,27 @@ let mut sale = SaleBuilder::new()
|
|||||||
sale.update_status(SaleStatus::Completed);
|
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>),
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user