forge/lib/services/src/bank.rs
Jacob Schmidt ebfe77a340 feat: implement complete Forge framework with Rust/Redis backend and Arma 3 integration
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>
2026-01-04 12:52:15 -06:00

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)
}
}