Implemented features: - High-performance Rust extension with Redis persistence - Actor/player management with loadout, position, and state tracking - Banking system with deposit, withdraw, and transfer operations - Physical and virtual garage/locker systems for vehicle and equipment storage - Organization management with member tracking and permissions - Client-side UI with React-like state management - Server-side event-driven architecture with CBA Events - Security: Self-transfer prevention at multiple layers - Logging system with per-module log files - ICOM module for inter-server communication Co-Authored-By: Warp <agent@warp.dev>
193 lines
7.4 KiB
Rust
193 lines
7.4 KiB
Rust
//! Bank service layer providing business logic for bank management operations.
|
|
//!
|
|
//! Implements the service layer of the bank management system, handling business logic,
|
|
//! validation, and orchestration.
|
|
//!
|
|
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
|
|
|
|
use forge_models::Bank;
|
|
use forge_repositories::BankRepository;
|
|
|
|
/// Service layer implementation for bank business logic and operations.
|
|
///
|
|
/// Orchestrates bank management operations, handling business logic, validation,
|
|
/// and data transformation. See [crate README](../README.md) for details.
|
|
///
|
|
/// # Thread Safety
|
|
/// Thread-safe when used with a thread-safe repository.
|
|
pub struct BankService<R: BankRepository> {
|
|
/// The repository instance used for all data persistence operations.
|
|
///
|
|
/// This repository handles the actual storage and retrieval of bank data,
|
|
/// abstracting away the specific database implementation details.
|
|
repository: R,
|
|
}
|
|
|
|
impl<R: BankRepository> BankService<R> {
|
|
/// Creates a new bank service with the provided repository.
|
|
///
|
|
/// The repository must be initialized and ready for use.
|
|
pub fn new(repository: R) -> Self {
|
|
Self { repository }
|
|
}
|
|
|
|
/// Creates a new bank with the provided ID and JSON data.
|
|
///
|
|
/// Handles validation, duplicate checking, and persistence.
|
|
/// See [crate README](../README.md) for JSON format and business rules.
|
|
pub fn create(&self, key: String, json_data: String) -> Result<Bank, String> {
|
|
// Parse JSON data to Bank struct
|
|
let mut bank: Bank =
|
|
serde_json::from_str(&json_data).map_err(|e| format!("Invalid Bank JSON: {}", e))?;
|
|
|
|
// Set UID from parameter (authoritative source)
|
|
bank.uid = key;
|
|
|
|
// Check if bank already exists to prevent duplicates
|
|
if self.repository.exists(&bank.uid)? {
|
|
return Err(format!("Bank with uid '{}' already exists", bank.uid));
|
|
}
|
|
|
|
if let Err(e) = bank.validate() {
|
|
return Err(format!("Invalid Bank JSON: {}", e));
|
|
}
|
|
|
|
self.repository.create(&bank)?;
|
|
|
|
Ok(bank)
|
|
}
|
|
|
|
/// Retrieves an bank by their unique identifier with automatic fallback creation.
|
|
///
|
|
/// Implements a "get-or-create" pattern: if the bank doesn't exist, a new one
|
|
/// with default values is returned (but not persisted).
|
|
pub fn get_bank(&self, key: String) -> Result<Bank, String> {
|
|
// Attempt to retrieve bank from repository
|
|
match self.repository.get_by_id(&key)? {
|
|
// Bank found - return it
|
|
Some(bank) => Ok(bank),
|
|
// Bank not found - create fallback bank with default values
|
|
None => Err(format!("Bank with UID '{}' not found", key)),
|
|
}
|
|
}
|
|
|
|
/// Updates an existing bank with new data from JSON.
|
|
///
|
|
/// Handles partial updates, validation, and persistence.
|
|
/// See [crate README](../README.md) for JSON format and concurrency details.
|
|
pub fn update_bank(&self, key: String, json_update: String) -> Result<Bank, String> {
|
|
// Retrieve existing bank from repository
|
|
let mut bank = match self.repository.get_by_id(&key)? {
|
|
Some(bank) => bank,
|
|
None => return Err(format!("Bank with UID '{}' not found", key)),
|
|
};
|
|
|
|
// Parse and validate JSON update data
|
|
let update_data: serde_json::Value =
|
|
serde_json::from_str(&json_update).map_err(|e| format!("Invalid JSON: {}", e))?;
|
|
|
|
// Ensure update data is a JSON object
|
|
if !update_data.is_object() {
|
|
return Err("Update data must be a JSON object".to_string());
|
|
}
|
|
|
|
// Create a temporary copy to safely apply updates with validation
|
|
let mut updated_bank = bank.clone();
|
|
|
|
// Apply updates field by field
|
|
if let Some(obj) = update_data.as_object() {
|
|
for (field, value) in obj {
|
|
match field.as_str() {
|
|
"uid" => {
|
|
// Skip UID - it's immutable and set by the system
|
|
continue;
|
|
}
|
|
"name" => {
|
|
if let Some(name) = value.as_str() {
|
|
updated_bank.name = name.to_string();
|
|
} else {
|
|
return Err("Name must be a string".to_string());
|
|
}
|
|
}
|
|
"bank" => {
|
|
if let Some(bank_val) = value.as_f64() {
|
|
updated_bank.bank = bank_val;
|
|
} else {
|
|
return Err("Bank balance must be a number".to_string());
|
|
}
|
|
}
|
|
"cash" => {
|
|
if let Some(cash_val) = value.as_f64() {
|
|
updated_bank.cash = cash_val;
|
|
} else {
|
|
return Err("Cash must be a number".to_string());
|
|
}
|
|
}
|
|
"earnings" => {
|
|
if let Some(earnings_val) = value.as_f64() {
|
|
updated_bank.earnings = earnings_val;
|
|
} else {
|
|
return Err("Earnings must be a number".to_string());
|
|
}
|
|
}
|
|
"pin" => {
|
|
if let Some(pin_val) = value.as_u64() {
|
|
updated_bank.pin = pin_val;
|
|
} else {
|
|
return Err("PIN must be a number".to_string());
|
|
}
|
|
}
|
|
"transactions" => {
|
|
if let Some(arr) = value.as_array() {
|
|
updated_bank.transactions = arr
|
|
.iter()
|
|
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
|
.collect();
|
|
} else {
|
|
return Err("Transactions must be an array".to_string());
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(format!("Unknown field: {}", field));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate the updated bank before committing changes
|
|
updated_bank
|
|
.validate()
|
|
.map_err(|e| format!("Validation failed: {}", e))?;
|
|
|
|
// Only commit changes after validation passes
|
|
bank = updated_bank;
|
|
|
|
// Persist the updated bank to repository
|
|
self.repository.update(&bank)?;
|
|
|
|
Ok(bank)
|
|
}
|
|
|
|
/// Permanently deletes an bank from the system.
|
|
///
|
|
/// Irreversible operation. Delegates to repository.
|
|
pub fn delete_bank(&self, key: String) -> Result<(), String> {
|
|
// Delegate deletion to repository layer
|
|
// Future enhancements could add business logic here:
|
|
// - Authorization checks
|
|
// - Audit logging
|
|
// - Cascade deletion
|
|
// - Soft deletion
|
|
self.repository.delete(&key)
|
|
}
|
|
|
|
/// Checks if an bank exists in the system.
|
|
///
|
|
/// Lightweight check without data retrieval.
|
|
pub fn bank_exists(&self, key: String) -> Result<bool, String> {
|
|
// Delegate existence check to repository layer
|
|
// This is a lightweight operation that doesn't retrieve data
|
|
self.repository.exists(&key)
|
|
}
|
|
}
|