## Summary This finishes the org credit line workflow so it behaves like reserved treasury-backed credit instead of a simple member allowance. ## What changed - reserve org funds immediately when a credit line is assigned - track credit lines with: - approved amount - available amount - outstanding principal - interest rate - amount due - consume reserved credit during store checkout without charging org funds a second time - add credit line repayment through the bank app - sync richer credit line state into org and bank payloads/UI - keep legacy `amount` compatibility mapped to available credit for older consumers ## User-facing behavior - assigning a credit line now reduces available org funds immediately - spending on `credit_line` reduces available credit and creates debt with interest - the bank app now shows outstanding credit debt and allows repayment from personal bank funds - the org treasury view now shows reserved credit and outstanding due totals ## Validation - `cargo fmt` - `npm run build:webui` - `cargo test -p forge-services --quiet` - `cargo test -p forge-server --quiet` ## Follow-up checks - validate in-game that assigning a credit line reduces org funds immediately - validate store checkout with `credit_line` updates available credit and debt correctly - validate bank repayment decreases player bank balance, increases org funds, and reduces amount due Co-authored-by: Jacob Schmidt <innovativestudios@outlook.com> Reviewed-on: #2
Forge Library
This directory contains the core business logic and data layers for the Forge framework, organized into modular, reusable crates that follow clean architecture principles.
Architecture Overview
The library follows a layered architecture pattern, ensuring separation of concerns and maintainability:
graph TD
Extension[Extension Layer<br/>#40;ArmA 3 Interface#41;]
Services[Services Layer<br/>#40;Business Logic#41;]
Repositories[Repositories Layer<br/>#40;Data Persistence#41;]
Models[Models Layer<br/>#40;Data Structures#41;]
Extension --> Services
Services --> Repositories
Repositories --> Models
Modules
Models (lib/models)
Purpose: Defines strict data structures and validation rules for domain entities.
Responsibilities:
- Define entity structures (
Actor,Org) - Implement validation logic
- Handle serialization/deserialization (JSON, Arma)
- Enforce business rules at the data level
Key Features:
- Strong typing with Rust structs
- Built-in validation on creation and updates
- Automatic email generation for actors
- Arma-specific type conversions
Documentation: models/README.md
Repositories (lib/repositories)
Purpose: Manages data persistence and retrieval using Redis.
Responsibilities:
- Abstract database operations
- Implement CRUD operations
- Handle data serialization to Redis formats
- Manage Redis keys and data structures
Key Features:
- Generic over Redis client implementations
- Hash-based storage for structured data
- Set-based storage for collections (e.g., org members)
- Thread-safe operations (
Send + Sync)
Documentation: repositories/README.md
Services (lib/services)
Purpose: Implements business logic, validation, and orchestration of operations.
Responsibilities:
- Coordinate between repositories
- Enforce business rules
- Handle complex workflows
- Provide high-level APIs for the extension layer
Key Features:
- Generic over repository implementations
- Stateless service design
- Get-or-create patterns for entities
- Comprehensive error handling
Documentation: services/README.md
Shared (lib/shared)
Purpose: Provides common utilities, traits, and helper functions used across all layers.
Responsibilities:
- Define shared traits (
RedisClient) - Provide utility functions
- Common type definitions
- Cross-cutting concerns
Key Features:
RedisClienttrait for repository abstraction- JSON/Redis value parsing utilities
- Arma value conversion helpers
- Reusable helper functions
How It All Works Together
Example: Creating an Actor
Here's how the layers interact when creating a new actor:
-
Extension Layer receives SQF command:
// arma/server/extension/src/actor.rs pub fn create_actor(key: String, data: String) -> String { // Parse JSON and call service ACTOR_SERVICE.create_actor(uid, json_data) } -
Service Layer validates and orchestrates:
// lib/services/src/actor.rs impl<R: ActorRepository> ActorService<R> { pub fn create_actor(&self, uid: String, data: String) -> Result<Actor, String> { // Create actor model (validates data) let actor = Actor::new(uid, data)?; // Persist via repository self.repository.create(&actor)?; Ok(actor) } } -
Repository Layer persists to Redis:
// lib/repositories/src/actor.rs impl<C: RedisClient> ActorRepository for RedisActorRepository<C> { fn create(&self, actor: &Actor) -> Result<(), String> { // Convert actor to Redis hash let fields = actor.to_redis_fields(); // Store in Redis self.client.hash_mset(format!("actor:{}", actor.uid), fields) } } -
Model Layer ensures data integrity:
// lib/models/src/actor.rs impl Actor { pub fn new(uid: String, data: String) -> Result<Self, String> { // Validate all fields Self::validate(&uid, &data)?; // Create actor with validated data Ok(Actor { uid, /* ... */ }) } }
Contributing
We welcome contributions to the Forge library! Follow these guidelines to maintain consistency and quality.
Adding a New Model
See models/README.md - Contributing
Summary:
- Define struct with validation rules
- Implement
newandvalidatemethods - Add serialization traits (
Serialize,Deserialize) - Implement Arma conversions (
FromArma,IntoArma)
Adding a New Repository
See repositories/README.md - Contributing
Summary:
- Define repository trait with
Send + Sync - Implement trait for
RedisXRepository<C: RedisClient> - Use
forge_shared::RedisClientfor operations - Register module in
lib.rs
Adding a New Service
See services/README.md - Contributing
Summary:
- Create service struct generic over repository
- Implement constructor and business logic methods
- Delegate data operations to repository
- Register module in
lib.rs
Best Practices
Separation of Concerns
- Models: Only data structures and validation
- Repositories: Only data persistence logic
- Services: Only business logic and orchestration
- Shared: Only common utilities and traits
Error Handling
- Use
Result<T, String>for all fallible operations - Provide descriptive error messages
- Propagate errors up the stack with
?
Testing
- Models: Test validation rules
- Repositories: Test with mock Redis clients
- Services: Test with mock repositories
- Integration: Test full stack in extension layer
Dependencies
- Models should have minimal dependencies
- Repositories depend on models and shared
- Services depend on repositories and models
- Avoid circular dependencies
Thread Safety
- All repository traits require
Send + Sync - Services are stateless and thread-safe
- Use appropriate synchronization primitives when needed
Module Dependencies
graph TD
Shared[Shared<br/>#40;No Dependencies#41;]
Models[Models<br/>#40;Depends on Shared#41;]
Repositories[Repositories<br/>#40;Depends on Models, Shared#41;]
Services[Services<br/>#40;Depends on Repositories, Models#41;]
Extension[Extension<br/>#40;Depends on Services, Repositories, Models, Shared#41;]
Shared --> Services
Services --> Repositories
Repositories --> Models
Models --> Extension
Development Workflow
- Define Model: Start with data structure and validation
- Create Repository: Implement persistence layer
- Build Service: Add business logic
- Expose in Extension: Create SQF-callable commands
- Test: Verify each layer independently and together