diff --git a/Architecture_Diagram.md b/Architecture_Diagram.md
index 85521aa..329d53a 100644
--- a/Architecture_Diagram.md
+++ b/Architecture_Diagram.md
@@ -1,134 +1,49 @@
-# Forge Architecture & Data Flow Diagram
+# Forge Architecture
-## ๐๏ธ **System Architecture Overview**
-
-```mermaid
-graph TD
- subgraph ForgeSystem [FORGE SYSTEM]
- subgraph Clients [Clients #40;Read-Only#41;]
- ClientA[CLIENT A]
- ClientB[CLIENT B]
- ClientN[CLIENT N]
-
- subgraph OptimisticCache [Optimistic Cache]
- ActorObj[Actor Object
- loadout
- position
- stats]
- end
-
- ClientA --- OptimisticCache
- ClientB --- OptimisticCache
- ClientN --- OptimisticCache
- end
-
- subgraph Server [ArmA 3 SERVER #40;Hot Cache#41;]
- Registry["GVAR(Registry)
In-Memory HashMap
UID -> {loadout, position, stats...}"]
- SessionMgmt[Session Management
- Token Generation
- UID Resolution
- Player State]
- end
-
- subgraph Rust [EXTENSION #40;Cold Storage#41;]
- ConnPool["Connection Pool
(bb8-redis)
2-10 connections"]
- RedisOps[Redis Operations
- actor_get/set/update
- Async I/O]
- end
-
- subgraph Redis [DATABASE #40;Saved to Disc#41;]
- ActorDataStore[Actor Data Store
actor:UID -> JSON]
- Modules[Additional Modules
garage, locker, bank, org]
- end
-
- Clients -->|Event Driven
#40;CBA A3 Events#41;| Server
- Server -->|Extension Calls
#40;Rust FFI#41;| Rust
- Rust -->|Redis Protocol
#40;bb8-redis#41;| Redis
- end
-```
-
-## ๐ **Data Flow Sequence**
-
-### **1. Player Connection & Initial Data Load**
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Server as Server (Hot Cache)
- participant Extension as Extension (Cold Storage)
- participant Redis as Redis (Database)
-
- Note over Client, Redis: 1. Player Connection & Initial Data Load
-
- Client->>Server: 1. Connect
- Client->>Server: 2. Request Actor Data
- Server->>Server: 3. Check Cache (Cache Miss)
- Server->>Extension: 4. Extension Call
- Extension->>Redis: 5. Redis Query
- Redis-->>Extension: 6. JSON Data
- Extension-->>Server: 7. Actor Data
- Server->>Server: 8. Store in Hot Cache
- Server-->>Client: 9. Secure Response
- Client->>Client: 10. Update Local Cache
-```
-
-### **2. Subsequent Data Access (Cache Hit)**
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Server as Server (Hot Cache)
- participant Extension as Extension (Cold Storage)
- participant Redis as Redis (Database)
-
- Note over Client, Redis: 2. Subsequent Data Access (Cache Hit)
-
- Client->>Server: 1. Request Actor Data
- Server->>Server: 2. Check Cache (Cache Hit!)
- Server-->>Client: 3. Instant Response
- Client->>Client: 4. Update Local Cache
-```
-
-### **3. Data Update (Write-Through)**
-
-```mermaid
-sequenceDiagram
- participant Client
- participant Server as Server (Hot Cache)
- participant Extension as Extension (Cold Storage)
- participant Redis as Redis (Database)
-
- Note over Client, Redis: 3. Data Update (Write-Through)
-
- Client->>Server: 1. Action (Move, etc)
- Server->>Server: 2. Validate & Update Cache
- Server->>Extension: 3. Persist to Database
- Extension->>Redis: 4. Redis Update
- Redis-->>Extension: 5. Confirmation
- Extension-->>Server: 6. Success
- Server-->>Client: 7. Sync to All Clients
-```
-
-## ๐ **Performance Characteristics**
-
-### **Access Times**
-
-- **Hot Cache (Server)**: `< 1ms` (HashMap lookup)
-- **Cold Storage (Redis)**: `1-5ms` (Network + Redis)
-- **Client Cache**: `< 0.1ms` (Local object access)
-
-### **Cache Hit Ratios**
-
-- **Hot Cache**: `~95%` (Active players)
-- **Cold Storage**: `~5%` (New connections, cache misses)
-
-### **Memory Usage**
-
-- **Server Registry**: `~1KB per active player`
-- **Client Cache**: `~500B per player object`
-- **Redis**: `~2KB per player (persistent)`
-
-## ๐ **Security & Session Management**
+## Runtime Flow
```mermaid
flowchart TD
- subgraph SessionMgmt [SERVER-SIDE #40;Session MGT#41;]
- Conn[Player Connection] --> Token[Session Token Generation
#40;Generated on server#41;]
- Token --> UID[UID Resolution
#40;Steam UID mapping#41;]
- UID --> State[Player State Tracking
#40;Tracked in Registry#41;]
- State --> Access[Data Access Authorized
#40;Authorized via session#41;]
- end
+ Client[Arma Client Addons] --> Server[Arma Server Addons]
+ Server --> Bridge[Extension Bridge]
+ Bridge --> Extension[Rust arma-rs Extension]
+ Extension --> Services[Service Layer]
+ Services --> Repositories[Repository Traits]
+ Repositories --> Surreal[(SurrealDB)]
+```
+
+## Persistence Startup
+
+```mermaid
+sequenceDiagram
+ participant Arma as Arma Server
+ participant Ext as Forge Extension
+ participant Db as SurrealDB
+
+ Arma->>Ext: init
+ Ext->>Db: connect
+ Ext->>Db: apply schema modules
+ Db-->>Ext: ready
+ Arma->>Ext: status
+ Ext-->>Arma: connected
+```
+
+## Data Access
+
+```mermaid
+sequenceDiagram
+ participant SQF as SQF Addon
+ participant Ext as Extension Command
+ participant Service as Service
+ participant Repo as Repository
+ participant Db as SurrealDB
+
+ SQF->>Ext: domain command
+ Ext->>Service: validate and execute
+ Service->>Repo: repository call
+ Repo->>Db: query/upsert/delete
+ Db-->>Repo: result
+ Repo-->>Service: domain model
+ Service-->>Ext: response
+ Ext-->>SQF: serialized result
```
diff --git a/Cargo.toml b/Cargo.toml
index 080237f..d08f21f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,6 @@ resolver = "3"
[workspace.dependencies]
arma-rs = { version = "1.11.15", features = ["chrono", "serde_json", "uuid"] }
chrono = "0.4.42"
-redis = "1.0.0-rc.1"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
tokio = { version = "1.47.1", features = ["full"] }
diff --git a/README.md b/README.md
index c97d5c2..2902ee8 100644
--- a/README.md
+++ b/README.md
@@ -1,313 +1,54 @@
-# Forge Framework
+# Forge
-**Forge** is a high-performance, production-ready framework for Arma 3 persistent game servers, built with Rust and Redis for optimal performance and reliability.
+Forge is a framework for Arma 3 persistent game servers. It combines SQF
+addons, a Rust `arma-rs` extension, shared service crates, and web-based client
+interfaces for player data, organizations, banking, garages, lockers, phones,
+CAD, stores, and task workflows.
-## Overview
+## Storage
-Forge provides a complete solution for managing persistent player data, organizations, and game state in Arma 3 multiplayer environments. It combines the performance of Rust with the flexibility of Redis to deliver sub-millisecond response times while maintaining data consistency across server restarts.
-
-### Key Features
-
-- **๐ High Performance**: Sub-millisecond data access through intelligent caching
-- **๐ Data Integrity**: Strict validation and type safety at every layer
-- **๐๏ธ Clean Architecture**: Layered design following SOLID principles
-- **๐ฆ Modular Design**: Easy to extend with new entities and features
-- **๐ Real-time Sync**: Automatic state synchronization across all clients
-- **๐พ Persistent Storage**: Redis-backed storage with automatic failover
-- **๐งช Testable**: Mock-friendly architecture for comprehensive testing
-
-## Architecture
-
-Forge follows a **layered architecture** pattern:
-
-```mermaid
-graph TD
- Extension[Extension Layer
ArmA 3 Interface <---> Rust]
- Services[Services Layer
#40;Business Logic#41;]
- Repositories[Repositories Layer
#40;Data Persistence#41;]
- Models[Models Layer
#40;Data Structures & Validation#41;]
-
- Extension --> Services
- Services --> Repositories
- Repositories --> Models
-```
-
-**Communication Flow**:
-
-- **Clients** โ Use events (`CBA_Events`) to communicate with server
-- **Server** โ Calls Rust extension via `callExtension`
-- **Extension** โ Manages Redis connection pool and data operations
-
-For detailed architecture information, see [Diagram](Architecture_Diagram.md).
-
-## Project Structure
-
-```
-forge/
-โโโ arma/
-โ โโโ client/ # Client-side SQF mod
-โ โ โโโ addons/
-โ โ โ โโโ main/ # Core initialization & config
-โ โ โ โโโ common/ # Shared utilities & helpers
-โ โ โ โโโ actor/ # Actor/player UI, class & events
-โ โ โ โโโ org/ # Organization UI, class & events
-โ โ โ โโโ bank/ # Banking UI, class & events
-โ โ โโโ include/ # Header files
-โ โ โโโ tools/ # Build tools
-โ โโโ server/
-โ โ โโโ addons/
-โ โ โ โโโ main/ # Core initialization & config
-โ โ โ โโโ common/ # Shared utilities & helpers
-โ โ โ โโโ actor/ # Actor/player Registry, Store & events
-โ โ โ โโโ org/ # Organization Registry, Store & events
-โ โ โโโ include/ # Header files
-โ โ โโโ tools/ # Build tools
-โ โ โโโ extension/ # Rust extension (Arma 3 interface)
-โ โ โโโ src/
-โ โ โ โโโ actor.rs # Actor/player commands
-โ โ โ โโโ org.rs # Organization commands
-โ โ โ โโโ redis/ # Redis operations module
-โ โ โ โโโ adapters/ # Repository adapters
-โ โ โโโ README.md
-โโโ lib/
-โ โโโ models/ # Data structures & validation
-โ โโโ repositories/ # Data persistence layer
-โ โโโ services/ # Business logic layer
-โ โโโ shared/ # Common utilities & traits
-โ โโโ README.md
-โโโ FORGE_Architecture_Diagram.md
-```
-
-## Quick Start
-
-### Prerequisites
-
-- Rust 1.70+ with `cargo`
-- Redis 6.0+
-- HEMTT
-
-1. Clone the repository from Gitea
-2. Install HEMTT
- The latest version of HEMTT can be installed by running:
-
-```cmd
-winget install hemtt
-```
-
-### Coding Guidelines
-
-This mod follows the same coding guidelines as the ACE3 mod, which can be found [here](https://ace3.acemod.org/wiki/development/coding-guidelines).
-
-### Building the Extension
-
-```bash
-# Build for release
-cargo build --release
-
-# The compiled extension will be at:
-# target/release/forge_server.dll (Windows)
-# target/release/forge_server.so (Linux)
-```
-
-### Configuration
-
-Create `@forge_server/config.toml`:
+Durable persistence is backed by SurrealDB. The server extension loads schema
+modules at startup and routes domain repositories through the SurrealDB client.
```toml
-[redis]
-host = "127.0.0.1"
-port = 6379
-password = "" # Optional
-max_connections = 10
-min_connections = 2
-idle_timeout = 300
+[surreal]
+endpoint = "127.0.0.1:8000"
+namespace = "forge"
+database = "main"
+username = "root"
+password = "root"
+connect_timeout_ms = 5000
```
-### SQF Usage
+## Workspace
+
+```text
+arma/
+ client/ Client-side addons and browser UIs
+ server/ Server-side addons and extension crate
+bin/
+ icom/ Interprocess communication helper
+lib/
+ models/ Shared domain models
+ repositories/ Repository traits and in-memory test stores
+ services/ Domain business logic
+ shared/ Cross-crate helpers
+tools/ Web UI build tooling
+```
+
+## Common Commands
+
+```powershell
+cargo test
+npm run build:webui
+.\build-arma.ps1
+```
+
+## Extension Status
```sqf
-// Create an actor
-private _data = createHashMapFromArray [
- ["name", "John Doe"],
- ["bank", 1000],
- ["level", 1]
-];
-private _result = "forge_server" callExtension ["actor:create", [getPlayerUID player, toJSON _data]];
-
-// Get actor data
-private _result = "forge_server" callExtension ["actor:get", [getPlayerUID player]];
-private _actorData = fromJSON (_result select 0);
-
-// Update actor
-private _update = createHashMapFromArray [["bank", 1500]];
-"forge_server" callExtension ["actor:update", [getPlayerUID player, toJSON _update]];
+"forge_server" callExtension ["status", []];
+"forge_server" callExtension ["surreal:status", []];
```
-## Core Modules
-
-### Models
-
-Defines strict data structures with built-in validation:
-
-- `Actor`: Player data (stats, inventory, position)
-- `Org`: Organization/clan data (members, roles, metadata)
-
-[Documentation](lib/models/README.md)
-
-### Repositories
-
-Manages data persistence with Redis:
-
-- Hash-based storage for structured data
-- Set-based storage for collections
-- Generic over Redis client implementations
-
-[Documentation](lib/repositories/README.md)
-
-### Services
-
-Implements business logic and orchestration:
-
-- Get-or-create patterns
-- Data validation and transformation
-- Complex workflows
-
-[Documentation](lib/services/README.md)
-
-### Extension
-
-Arma 3 interface layer:
-
-- Command routing and parsing
-- Session management
-- Error handling and logging
-
-[Documentation](arma/server/extension/README.md)
-
-### Client Mod
-
-Client-side SQF addon that provides:
-
-- **UI Components**: Player interfaces for inventory, organizations, banking
-- **Event Handlers**: CBA event listeners for server communication
-- **Optimistic Caching**: Local data caching for instant UI updates
-- **State Management**: Client-side state synchronization
-- **Input Validation**: Client-side validation before server requests
-
-The client mod communicates with the server using **CBA Events**, ensuring:
-
-- No direct extension calls from clients (security)
-- Event-driven architecture for scalability
-- Automatic state synchronization across all clients
-- Reduced server load through client-side caching
-
-## Available Commands
-
-### Actor Commands
-
-| Command | Description |
-| -------------- | -------------------------- |
-| `actor:get` | Retrieve actor data by UID |
-| `actor:create` | Create a new actor |
-| `actor:update` | Update actor fields |
-| `actor:exists` | Check if actor exists |
-| `actor:delete` | Delete actor data |
-
-### Organization Commands
-
-| Command | Description |
-| ------------------- | ------------------------------- |
-| `org:get` | Retrieve organization data |
-| `org:create` | Create a new organization |
-| `org:update` | Update organization fields |
-| `org:exists` | Check if organization exists |
-| `org:delete` | Delete organization |
-| `org:add_member` | Add member to organization |
-| `org:remove_member` | Remove member from organization |
-| `org:get_members` | Get all organization members |
-
-### Redis Operations
-
-Direct Redis operations for advanced use cases:
-
-- **Common**: Key-value operations (set, get, incr, decr, del)
-- **Hash**: Structured data (hset, hget, hgetall, hdel)
-- **List**: Ordered collections (lpush, rpush, lrange, lpop, rpop)
-- **Set**: Unique collections (sadd, smembers, srem, sismember)
-
-[Documentation](arma/server/extension/src/redis/README.md)
-
-## Performance
-
-- **Hot Cache (Server)**: < 1ms (HashMap lookup)
-- **Cold Storage (Redis)**: 1-5ms (Network + Redis query)
-- **Cache Hit Ratio**: ~95% for active players
-- **Memory Usage**: ~1KB per active player (server), ~2KB per player (Redis)
-
-## Contributing
-
-We welcome contributions! Please see the contributing guides for each layer:
-
-- [Extension Contributing Guide](arma/server/extension/README.md#contributing)
-- [Services Contributing Guide](lib/services/README.md#contributing)
-- [Repositories Contributing Guide](lib/repositories/README.md#contributing)
-- [Models Contributing Guide](lib/models/README.md#contributing)
-- [Library Contributing Guide](lib/README.md#contributing)
-- [Adapter Contributing Guide](arma/server/extension/src/adapters/#contributing)
-
-### Development Workflow
-
-1. **Define Model**: Create data structure with validation
-2. **Create Repository**: Implement persistence layer
-3. **Build Service**: Add business logic
-4. **Expose in Extension**: Create SQF-callable commands
-5. **Test**: Verify each layer independently
-
-## Error Handling
-
-All commands return consistent error messages:
-
-```sqf
-private _result = "forge_server" callExtension ["actor:get", ["invalid_uid"]];
-private _response = _result select 0;
-
-if (_response find "Error:" == 0) then {
- diag_log format ["Operation failed: %1", _response];
-} else {
- private _data = fromJSON _response;
- // Use data
-};
-```
-
-## Logging
-
-Logs are automatically created in `@forge_server/logs/`:
-
-- `actor.log` - Actor operations
-- `org.log` - Organization operations
-- `redis.log` - Redis connection and operations
-- `debug.log` - General debug information
-
-## License
-
-View the License [here](LICENSE.md).
-
-## Support
-
-- **Issues**: [Gitea Issues](https://gitea.innovativedevsolutions.org/IDSolutions/forge/issues)
-- **Documentation**: See individual module READMEs
-- **Architecture**: [Diagram](Architecture_Diagram.md)
-
-## Roadmap
-
-- [ ] Admin system
-- [ ] Arsenal system
-- [ ] Banking system
-- [ ] Economy system
-- [ ] Garage system
-- [ ] Locker system
-- [ ] Mission template
-
----
-
-Built using **Rust**, **Redis**, and **Arma 3**
+Both commands report the persistence connection state.
diff --git a/arma/server/addons/extension/functions/fnc_extCall.sqf b/arma/server/addons/extension/functions/fnc_extCall.sqf
index 7cd5f8d..40754af 100644
--- a/arma/server/addons/extension/functions/fnc_extCall.sqf
+++ b/arma/server/addons/extension/functions/fnc_extCall.sqf
@@ -61,10 +61,6 @@ private _transportResponseFunctions = [
"org:assets:get",
"org:fleet:get"
];
-private _requiresRedis = !(_functionLower in ["status", "version"])
- && (_functionLower find "icom:" == 0)
- && (_functionLower find "terrain:" == 0);
-
private _callExtensionCommand = {
params [["_command", "", [""]], ["_commandArguments", [], [[]]]];
@@ -110,28 +106,6 @@ private _callExtensionCommand = {
[_response, _responseSuccess]
};
-private _checkRedisAvailability = {
- ("forge_server" callExtension ["status", []]) params [
- "_redisStatus",
- "_statusExtCode",
- "_statusArmaCode"
- ];
-
- private _statusSuccess = (_statusExtCode == 0) && (_statusArmaCode == 0 || _statusArmaCode == 301);
-
- if (!_statusSuccess) exitWith {
- ["WARNING", "Unable to determine Redis status before extension call", nil, nil] call EFUNC(common,log);
- ["Error: Redis status check failed", false]
- };
-
- if (_redisStatus != "connected") exitWith {
- ["WARNING", format ["Blocked extension call '%1' because Redis status is '%2'", _function, _redisStatus], nil, nil] call EFUNC(common,log);
- [format ["Error: Redis is %1", _redisStatus], false]
- };
-
- ["", true]
-};
-
private _buildTransportArgumentsJson = {
private _rawArguments = _this;
if !(_rawArguments isEqualType []) then {
@@ -156,17 +130,6 @@ private _buildTransportArgumentsJson = {
format ["[%1]", _encodedArguments joinString ","]
};
-if (_requiresRedis) exitWith {
- [_function, _arguments] call _checkRedisAvailability params ["_redisResult", "_redisSuccess"];
- if (!_redisSuccess) exitWith { [_redisResult, false] };
-
- if (_functionLower in ["status", "version"]) exitWith {
- [_function, _arguments] call _callExtensionCommand
- };
-
- [_function, _arguments] call _callExtensionCommand
-};
-
if (_functionLower in ["status", "version"]) exitWith {
[_function, _arguments] call _callExtensionCommand
};
diff --git a/arma/server/config.example.toml b/arma/server/config.example.toml
index cce0abb..e25d1a4 100644
--- a/arma/server/config.example.toml
+++ b/arma/server/config.example.toml
@@ -1,41 +1,10 @@
-# Crate Server Configuration
-# Copy this file to config.toml and modify as needed
-# Place this file in the same directory as your crate_server_x64.dll
+# Forge Server Configuration
+# Copy this file to config.toml and place it beside forge_server_x64.dll.
-[redis]
-# Redis server connection settings
-host = "127.0.0.1"
-port = 6379
-db = 0 # Redis database number (0-15)
-
-# Optional authentication
-# username = "your_username"
-# password = "your_password"
-
-# Optional connection pool settings
-max_connections = 10 # Maximum number of connections in pool
-min_connections = 2 # Minimum number of idle connections
-idle_timeout = 60 # Idle connection timeout in seconds
-connect_timeout_ms = 2000 # Pool connect timeout in milliseconds
-pool_get_timeout_ms = 2000 # Pool checkout timeout in milliseconds
-command_timeout_ms = 2000 # Redis command timeout in milliseconds
-
-# Example configurations for different environments:
-
-# Development (local Redis)
-# host = "127.0.0.1"
-# port = 6379
-# max_connections = 5
-# min_connections = 1
-
-# Production (remote Redis with auth)
-# host = "redis.example.com"
-# port = 6379
-# username = "arma_server"
-# password = "secure_password_here"
-# max_connections = 20
-# min_connections = 5
-# idle_timeout = 30
-# connect_timeout_ms = 5000
-# pool_get_timeout_ms = 5000
-# command_timeout_ms = 5000
+[surreal]
+endpoint = "127.0.0.1:8000"
+namespace = "forge"
+database = "main"
+username = "root"
+password = "root"
+connect_timeout_ms = 5000
diff --git a/arma/server/docs/README.md b/arma/server/docs/README.md
index 350a651..41a834e 100644
--- a/arma/server/docs/README.md
+++ b/arma/server/docs/README.md
@@ -1,285 +1,39 @@
-# Forge Server - Redis Client Module
+# Forge Server Extension
-A high-performance arma-rs extension for Arma 3, featuring a **low-level Redis data access layer** that provides raw Redis operations as a foundation for higher-level game modules.
+Forge Server is an arma-rs extension for Arma 3 server-side persistence and
+domain services. It exposes game-facing commands and stores durable state in
+SurrealDB.
-## ๐ฏ Overview
+## Architecture
-The Forge Server Redis module is designed as a **foundational data access layer** that:
+SQF modules call `forge_server` through `fnc_extCall`. Small requests use the
+direct `callExtension` path, while large payloads are staged through the
+transport layer.
-- **Returns raw Redis responses** for maximum performance and flexibility
-- **Serves as the foundation** for higher-level game modules (actor, garage, locker, bank, etc.)
-- **Provides connection pooling** and error handling for Redis operations
-- **Enables persistent data storage** across server restarts
-- **Supports cross-server data sharing** in multi-server environments
-
-## ๐๏ธ Layered Architecture
-
-```
-SQF Scripts
- โ (JSON responses)
-Game Modules (actor, garage, locker, bank)
- โ (raw Redis responses)
-Redis Client Module
- โ (Redis protocol)
-Redis Server
+```text
+SQF module
+ -> extension bridge
+ -> domain command
+ -> service layer
+ -> repository
+ -> SurrealDB
```
-**This module handles the bottom layer** - raw Redis operations with connection pooling and error handling.
+## Configuration
-## ๐๏ธ Internal Architecture
-
-```
-forge_server_x64.dll Extension
-โโโ lib.rs # Core extension initialization & global runtime
-โโโ config.example.toml # Example configuration file
-โโโ redis/ # Redis Client module
- โโโ mod.rs # Group definitions & module exports
- โโโ client.rs # Connection pool management
- โโโ config.rs # Configuration system
- โโโ macros.rs # redis_operation! macro for boilerplate elimination
- โโโ common.rs # String/key operations
- โโโ hash.rs # Hash operations (HSET, HGET, etc.)
- โโโ list.rs # List operations (LPUSH, LPOP, etc.)
- โโโ set.rs # Set operations (SADD, SMEMBERS, etc.)
-```
-
-### Key Components
-
-- **lib.rs**: Manages global Redis pool and single Tokio runtime
-- **macros.rs**: Provides `redis_operation!` macro to eliminate boilerplate
-- **Operation modules**: Focus purely on Redis logic using the macro
-- **Synchronous Interface**: All functions appear synchronous to Arma while using async Redis internally
-
-## ๐ Features
-
-### Raw Redis Operations
-
-- **String Operations**: SET, GET, INCR, DECR, DEL, KEYS
-- **Hash Operations**: HSET, HGET, HMSET, HGETALL, HDEL, HKEYS, HVALS, HLEN
-- **List Operations**: LSET, LGET, LLEN, LRANGE, LPUSH, RPUSH, LPOP, RPOP, LTRIM, LREM
-- **Set Operations**: SADD, SMEMBERS, SCARD, SREM, SISMEMBER, SPOP, SRANDMEMBER
-
-### Performance Features
-
-- **Connection Pooling**: bb8-redis pool with configurable size and timeouts
-- **Single Runtime**: One shared Tokio runtime for all async operations
-- **Macro-Based**: `redis_operation!` macro eliminates boilerplate while maintaining performance
-- **Synchronous Interface**: Functions block until completion, compatible with Arma's threading model
-- **Raw Responses**: Returns native Redis values for maximum performance
-- **Thread Safety**: Safe concurrent access from multiple Arma threads
-
-## โ๏ธ Configuration System
-
-Forge Server uses a TOML-based configuration system for flexible Redis connection management.
-
-### Configuration File
-
-Create a `config.toml` file in your extension directory:
+Copy `config.example.toml` to `config.toml` next to the extension DLL.
```toml
-[redis]
-host = "127.0.0.1"
-port = 6379
-# db = 0 # Optional: Redis database number
-# username = "user" # Optional: Redis username
-# password = "password" # Optional: Redis password
-# max_connections = 10 # Optional: Maximum connections in pool
-# min_connections = 2 # Optional: Minimum idle connections in pool
-# idle_timeout = 60 # Optional: Connection idle timeout (seconds)
+[surreal]
+endpoint = "127.0.0.1:8000"
+namespace = "forge"
+database = "main"
+username = "root"
+password = "root"
+connect_timeout_ms = 5000
```
-### Fallback Behavior
+## References
-The extension uses a robust fallback system:
-
-1. **Loads `config.toml`** if present in the extension directory
-2. **Falls back to defaults** if configuration fails or file is missing
-3. **Only fails** if both config and defaults cannot establish connection
-
-**Default Settings:**
-
-- **Host**: `127.0.0.1`
-- **Port**: `6379`
-- **Max Connections**: `10`
-- **Min Connections**: `2`
-- **Idle Timeout**: `60 seconds`
-
-### Common Configurations
-
-**Development (Local Redis)**:
-
-```toml
-[redis]
-host = "127.0.0.1"
-port = 6379
-max_connections = 5
-min_connections = 1
-```
-
-**Production (Remote Redis with Authentication)**:
-
-```toml
-[redis]
-host = "redis.example.com"
-port = 6379
-username = "arma_server"
-password = "secure_password"
-max_connections = 20
-min_connections = 5
-idle_timeout = 60
-```
-
-### Troubleshooting
-
-**Connection Issues:**
-
-- Verify Redis server is running: `redis-cli ping`
-- Check host/port settings in `config.toml`
-- Ensure firewall allows connection
-
-**Authentication Issues:**
-
-- Verify username/password in config
-- Check Redis server auth settings
-
-**Config File Issues:**
-
-- Check TOML syntax with online validators
-- Ensure quotes are properly closed
-- Verify file permissions
-
-**Connection Pool Benefits:**
-
-- Pre-warmed connections for zero-latency operations
-- Automatic connection recovery on network issues
-- Resource-efficient connection sharing
-- Configurable pool sizing for different deployment scenarios
-
-## ๐ง Installation
-
-1. **Prerequisites**:
- - Redis server (local or remote)
- - Arma 3 server with extension support
-
-2. **Extension Setup**:
- - Build the extension: `cargo build --release`
- - Copy the compiled `forge_server_x64.dll` to your Arma 3 server
- - Copy `config.example.toml` to `config.toml` and configure as needed
- - Load in server config or mission
-
-3. **Redis Server**:
-
- ```bash
- # Start Redis server
- redis-server
-
- # Verify connection
- redis-cli ping
- ```
-
-## ๐ Documentation
-
-- **[API Reference](./api-reference.md)** - Complete Redis command reference
-- **[Usage Examples](./usage-examples.md)** - Practical SQF integration examples
-
-## ๐ Performance
-
-- **Connection Pool**: 2-10 persistent connections using bb8-redis
-- **Single Runtime**: One shared Tokio runtime eliminates overhead from multiple runtimes
-- **Macro Efficiency**: Zero-cost abstraction โ macros expand to optimal code at compile time
-- **Synchronous Blocking**: Functions use `block_on()` for Arma compatibility without sacrificing async I/O benefits
-- **Response Format**: Raw Redis responses for minimal overhead
-- **Thread Safety**: Multiple Arma threads can safely call operations concurrently
-- **Memory Efficient**: Minimal resource usage per operation
-
-## ๐ Response Format
-
-This module returns **raw Redis responses** as strings for maximum performance:
-
-### Success Responses
-
-- **String values**: `"John"` (raw string)
-- **Numbers**: `"42"` (number as string)
-- **Lists/Arrays**: `"item1,item2,item3"` (comma-separated)
-- **Hashes**: `"key1,value1,key2,value2"` (comma-separated key-value pairs)
-- **Boolean**: `"1"` or `"0"` (for exists checks)
-- **Status**: `"OK"` (for successful SET operations)
-
-### Error Responses
-
-- **Format**: `"Error: "`
-- **Pool errors**: `"Error: Redis pool not initialized"`
-- **Connection errors**: `"Error: "`
-- **Redis errors**: `"Error: "`
-
-### Higher-Level JSON Formatting
-
-Game modules (actor, garage, etc.) will wrap these raw responses in structured JSON for SQF consumption.
-
-## โ๏ธ Macro-Based Implementation
-
-This extension uses a **macro-based architecture** to eliminate boilerplate while maintaining performance:
-
-### The `redis_operation!` Macro
-
-```rust
-pub fn set_key(key: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.set::<_, _, ()>(&key, &value).await {
- Ok(()) => "OK".to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-```
-
-### What the Macro Handles
-
-- **Pool Management**: Retrieves Redis connection pool
-- **Error Handling**: Returns "Error: ..." for pool/connection failures
-- **Async Bridging**: Uses shared Tokio runtime via `block_on()`
-- **Connection Acquisition**: Gets connection from pool with error handling
-- **Cleanup**: Automatic connection return to pool
-
-### Benefits
-
-- **Reduced Code**: 70% less boilerplate per function
-- **Consistency**: Identical error handling across all operations
-- **Maintainability**: Changes to connection logic in one place
-- **Performance**: No runtime overhead from abstraction
-- **Synchronous Interface**: Functions block until Redis operation completes
-
-## ๐ ๏ธ Development
-
-- **Language**: Rust (Edition 2024)
-- **Dependencies**: arma-rs, bb8-redis, redis, tokio
-- **Architecture**: Macro-based design with single runtime and connection pool
-- **Key Patterns**:
- - Global state management in `lib.rs`
- - Boilerplate elimination via `redis_operation!` macro
- - Synchronous interfaces over async operations
- - Raw Redis responses for minimal overhead
-- **Testing**: Unit tests for core functionality
-
-## ๐จ Error Handling
-
-The extension provides comprehensive error handling:
-
-- Connection failures
-- Redis operation errors
-- Invalid parameters
-- Pool initialization errors
-
-All errors include descriptive messages for debugging.
-
-## ๐ Monitoring
-
-Connection pool status and Redis operations can be monitored through:
-
-- Extension logs
-- Redis server logs
-- Connection pool metrics
-
----
-
-**Built with โค๏ธ for the Arma 3 community**
+- [API Reference](./api-reference.md)
+- [Usage Examples](./usage-examples.md)
diff --git a/arma/server/docs/api-reference.md b/arma/server/docs/api-reference.md
index 47a3f05..7ad1f62 100644
--- a/arma/server/docs/api-reference.md
+++ b/arma/server/docs/api-reference.md
@@ -1,426 +1,35 @@
-# Redis Client API Reference
+# Forge Server API Reference
-Complete reference for **raw Redis operations** available in the Forge Server extension. This module returns native Redis values without JSON formatting.
+The Forge server extension exposes domain-oriented commands through
+`callExtension`. Persistent data is stored through the configured SurrealDB
+connection and schema modules.
-> **Note**: This is a low-level data access layer. Higher-level game modules (actor, garage, etc.) will provide structured JSON responses for SQF consumption.
-
-## ๐๏ธ Implementation
-
-All Redis operations are implemented using the `redis_operation!` macro for:
-
-- **Consistent Error Handling**: All functions return identical error formats
-- **Connection Management**: Automatic pool and connection handling
-- **Synchronous Interface**: Functions block until Redis operations complete
-- **Performance**: Zero-cost abstraction with compile-time optimization
-
-## ๐ Command Structure
-
-All redis client commands follow the pattern: `"forge_server" callExtension ["redis:command", [parameters]]`
-
-## ๐ Common Operations
-
-### SET - Store a key-value pair
-
-**Command**: `redis:common:set`
-**Parameters**: `[key, value]`
+## Core Commands
```sqf
-"forge_server" callExtension ["redis:common:set", ["player_name", "John"]]
+"forge_server" callExtension ["version", []];
+"forge_server" callExtension ["status", []];
+"forge_server" callExtension ["surreal:status", []];
```
-**Raw Response**: `"OK"`
-
-### GET - Retrieve a value by key
-
-**Command**: `redis:common:get`
-**Parameters**: `[key]`
-
-```sqf
-"forge_server" callExtension ["redis:common:get", ["player_name"]]
-```
-
-**Raw Response**: `"John"` (the actual stored value)
-
-### INCR - Increment a numeric value
-
-**Command**: `redis:common:incr`
-**Parameters**: `[key, increment_amount]`
-
-```sqf
-"forge_server" callExtension ["redis:common:incr", ["player_score", 10]]
-```
-
-**Raw Response**: `"110"` (the new value as string)
-
-### DECR - Decrement a numeric value
-
-**Command**: `redis:common:decr`
-**Parameters**: `[key, decrement_amount]`
-
-```sqf
-"forge_server" callExtension ["redis:common:decr", ["player_lives", 1]]
-```
-
-**Raw Response**: `"2"` (the new value as string)
-
-### DEL - Delete a key
-
-**Command**: `redis:common:del`
-**Parameters**: `[key]`
-
-```sqf
-"forge_server" callExtension ["redis:common:del", ["temp_data"]]
-```
-
-**Raw Response**: `"1"` (number of keys deleted)
-
-### KEYS - List all keys matching pattern
-
-**Command**: `redis:common:keys`
-**Parameters**: `[]` (currently returns all keys with "\*" pattern)
-
-```sqf
-"forge_server" callExtension ["redis:common:keys", []]
-```
-
-**Raw Response**: `"player_name,player_score,mission_state"` (comma-separated list)
-
-## ๐๏ธ Hash Operations
-
-### HSET - Set hash field
-
-**Command**: `redis:hash:set`
-**Parameters**: `[hash_key, field, value]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:set", ["player:123", "name", "John"]]
-```
-
-**Raw Response**: `"1"` (number of fields added)
-
-### HGET - Get hash field
-
-**Command**: `redis:hash:get`
-**Parameters**: `[hash_key, field]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:get", ["player:123", "name"]]
-```
-
-**Raw Response**: `"John"` (the field value)
-
-### HMSET - Set multiple hash fields
-
-**Command**: `redis:hash:mset`
-**Parameters**: `[hash_key, [[field1, value1], [field2, value2], ...]]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:mset", ["player:123", [["name", "John"], ["score", "100"]]]]
-```
-
-**Raw Response**: `"OK"`
-
-### HGETALL - Get all hash fields
-
-**Command**: `redis:hash:getall`
-**Parameters**: `[hash_key]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:getall", ["player:123"]]
-```
-
-**Raw Response**: `"name,John,score,100,level,5"` (comma-separated key-value pairs)
-
-### HDEL - Delete hash field
-
-**Command**: `redis:hash:del`
-**Parameters**: `[hash_key, field]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:del", ["player:123", "temp_field"]]
-```
-
-**Raw Response**: `"1"` (number of fields removed)
-
-### HKEYS - Get all hash field names
-
-**Command**: `redis:hash:keys`
-**Parameters**: `[hash_key]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:keys", ["player:123"]]
-```
-
-**Raw Response**: `"name,score,level"` (comma-separated field names)
-
-### HVALS - Get all hash values
-
-**Command**: `redis:hash:vals`
-**Parameters**: `[hash_key]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:vals", ["player:123"]]
-```
-
-**Raw Response**: `"John,100,5"` (comma-separated values)
-
-### HLEN - Get hash field count
-
-**Command**: `redis:hash:len`
-**Parameters**: `[hash_key]`
-
-```sqf
-"forge_server" callExtension ["redis:hash:len", ["player:123"]]
-```
-
-**Raw Response**: `"3"` (number of fields in hash)
-
-## ๐ List Operations
-
-### LSET - Set list element by index
-
-**Command**: `redis:list:set`
-**Parameters**: `[list_key, index, value]`
-
-```sqf
-"forge_server" callExtension ["redis:list:set", ["mission_queue", 0, "patrol_alpha"]]
-```
-
-**Raw Response**: `"OK"`
-
-### LGET - Get list element by index
-
-**Command**: `redis:list:get`
-**Parameters**: `[list_key, index]`
-
-```sqf
-"forge_server" callExtension ["redis:list:get", ["mission_queue", 0]]
-```
-
-**Raw Response**: `"patrol_alpha"` (the element value)
-
-### LLEN - Get list length
-
-**Command**: `redis:list:len`
-**Parameters**: `[list_key]`
-
-```sqf
-"forge_server" callExtension ["redis:list:len", ["mission_queue"]]
-```
-
-**Raw Response**: `"5"` (list length)
-
-### LRANGE - Get list elements in range
-
-**Command**: `redis:list:range`
-**Parameters**: `[list_key, start_index, end_index]`
-
-```sqf
-"forge_server" callExtension ["redis:list:range", ["mission_queue", 0, 2]]
-```
-
-**Raw Response**: `"patrol_alpha,escort_beta,defend_gamma"` (comma-separated values)
-
-### LPUSH - Add element to list head
-
-**Command**: `redis:list:lpush`
-**Parameters**: `[list_key, value]`
-
-```sqf
-"forge_server" callExtension ["redis:list:lpush", ["recent_actions", "player_joined"]]
-```
-
-**Raw Response**: `"6"` (new list length)
-
-### RPUSH - Add element to list tail
-
-**Command**: `redis:list:rpush`
-**Parameters**: `[list_key, value]`
-
-```sqf
-"forge_server" callExtension ["redis:list:rpush", ["mission_queue", "new_objective"]]
-```
-
-**Raw Response**: `"6"` (new list length)
-
-### LPOP - Remove and return element from list head
-
-**Command**: `redis:list:lpop`
-**Parameters**: `[list_key, count]`
-
-```sqf
-"forge_server" callExtension ["redis:list:lpop", ["recent_actions", 1]]
-```
-
-**Raw Response**: `"player_joined"` (removed element) or `"item1,item2"` (if count > 1)
-
-### RPOP - Remove and return element from list tail
-
-**Command**: `redis:list:rpop`
-**Parameters**: `[list_key, count]`
-
-```sqf
-"forge_server" callExtension ["redis:list:rpop", ["mission_queue", 1]]
-```
-
-**Raw Response**: `"new_objective"` (removed element) or `"item1,item2"` (if count > 1)
-
-### LTRIM - Trim list to specified range
-
-**Command**: `redis:list:trim`
-**Parameters**: `[list_key, start_index, end_index]`
-
-```sqf
-"forge_server" callExtension ["redis:list:trim", ["recent_actions", 0, 9]] // Keep only last 10 items
-```
-
-**Raw Response**: `"OK"`
-
-### LREM - Remove elements from list
-
-**Command**: `redis:list:del`
-**Parameters**: `[list_key, count, value]`
-
-```sqf
-"forge_server" callExtension ["redis:list:del", ["mission_queue", 1, "completed_mission"]]
-```
-
-**Raw Response**: `"1"` (number of elements removed)
-
-## ๐ฏ Set Operations
-
-### SADD - Add element to set
-
-**Command**: `redis:set:add`
-**Parameters**: `[set_key, value]`
-
-```sqf
-"forge_server" callExtension ["redis:set:add", ["online_players", "player_123"]]
-```
-
-**Raw Response**: `"1"` (1 if element was added, 0 if already existed)
-
-### SMEMBERS - Get all set members
-
-**Command**: `redis:set:members`
-**Parameters**: `[set_key]`
-
-```sqf
-"forge_server" callExtension ["redis:set:members", ["online_players"]]
-```
-
-**Raw Response**: `"player_123,player_456,player_789"` (comma-separated members)
-
-### SCARD - Get set size
-
-**Command**: `redis:set:card`
-**Parameters**: `[set_key]`
-
-```sqf
-"forge_server" callExtension ["redis:set:card", ["online_players"]]
-```
-
-**Raw Response**: `"3"` (number of elements in set)
-
-### SREM - Remove element from set
-
-**Command**: `redis:set:del`
-**Parameters**: `[set_key, value]`
-
-```sqf
-"forge_server" callExtension ["redis:set:del", ["online_players", "player_456"]]
-```
-
-**Raw Response**: `"1"` (1 if element was removed, 0 if didn't exist)
-
-### SISMEMBER - Check if element is in set
-
-**Command**: `redis:set:ismember`
-**Parameters**: `[set_key, value]`
-
-```sqf
-"forge_server" callExtension ["redis:set:ismember", ["online_players", "player_123"]]
-```
-
-**Raw Response**: `"1"` (1 if member exists, 0 if not)
-
-### SPOP - Remove and return random element
-
-**Command**: `redis:set:pop`
-**Parameters**: `[set_key]`
-
-```sqf
-"forge_server" callExtension ["redis:set:pop", ["available_missions"]]
-```
-
-**Raw Response**: `"mission_alpha"` (the removed element)
-
-### SRANDMEMBER - Get random element without removing
-
-**Command**: `redis:set:randmember`
-**Parameters**: `[set_key]`
-
-```sqf
-"forge_server" callExtension ["redis:set:randmember", ["available_missions"]]
-```
-
-**Raw Response**: `"mission_beta"` (a random element)
-
-### SRANDMEMBER - Get multiple random elements
-
-**Command**: `redis:set:randmembers`
-**Parameters**: `[set_key, count]`
-
-```sqf
-"forge_server" callExtension ["redis:set:randmembers", ["available_missions", 3]]
-```
-
-**Raw Response**: `"mission_alpha,mission_gamma,mission_delta"` (comma-separated random elements)
-
-## โ ๏ธ Error Responses
-
-All commands may return error responses in this format:
-
-**Raw Error Response**: `"Error: "`
-
-### Common Error Types
-
-- **Pool not initialized**: `"Error: Redis pool not initialized"`
-- **Connection failed**: `"Error: Connection refused (os error 61)"`
-- **Key not found**: `"Error: key not found"` (for operations on non-existent keys)
-- **Invalid type**: `"Error: WRONGTYPE Operation against a key holding the wrong kind of value"`
-- **Index out of range**: `"Error: index out of range"` (for list operations)
-
-### Error Handling in Game Modules
-
-Higher-level game modules should check if the response starts with `"Error: "` to distinguish between successful responses and errors.
-
-```json
-{
- "status": "error",
- "error": "Failed to connect to Redis server"
-}
-```
-
-Common error types:
-
-- **Connection errors**: Redis server unavailable
-- **Operation errors**: Invalid data type for operation
-- **Parameter errors**: Missing or invalid parameters
-- **Pool errors**: Connection pool exhausted
-
-## ๐ Response Fields
-
-### Common Fields
-
-- `status`: Always present - "success" or "error"
-- `key`: The Redis key being operated on
-- `error`: Error message (only on error responses)
-
-### Success-Specific Fields
-
-- `data`: The retrieved data (for GET operations)
-- `value`: The stored value (for SET operations)
-- `was_new`: Boolean indicating if operation created new data
-- `removed_count`: Number of elements removed
-- `fields_set`: Number of fields set in hash operations
+`status` and `surreal:status` return `initializing`, `connected`, or `failed`.
+
+## Domain Commands
+
+Game systems should call the domain APIs instead of raw database operations:
+
+- `actor:*`
+- `bank:*`
+- `garage:*`
+- `locker:*`
+- `org:*`
+- `phone:*`
+- `store:*`
+- `task:*`
+- `cad:*`
+- `owned:garage:*`
+- `owned:locker:*`
+- `transport:*`
+
+Large request and response payloads are routed through the transport layer when
+needed by `forge_server_addons_extension_fnc_extCall`.
diff --git a/arma/server/docs/usage-examples.md b/arma/server/docs/usage-examples.md
index 026450c..c766ea2 100644
--- a/arma/server/docs/usage-examples.md
+++ b/arma/server/docs/usage-examples.md
@@ -1,437 +1,47 @@
-# Redis Client Usage Examples
+# Forge Server Usage Examples
-Practical examples of using the **raw Redis client module** as a foundation for higher-level game modules. These examples show low-level Redis operations that would typically be wrapped by game-specific modules (actor, garage, locker, bank).
+These examples use the domain command surface exposed by the extension.
+Persistence is handled by the server through SurrealDB.
-> **Note**: These examples show raw Redis responses. In practice, your game modules would wrap these calls and return structured JSON to SQF scripts.
-
-## ๐ Function Behavior
-
-All Redis functions are **synchronous from SQF's perspective**:
-
-- Functions **block** until Redis operation completes
-- **No callbacks** or async handling needed in SQF
-- **Direct return values** โ either data or error strings
-- **Thread-safe** โ multiple scripts can call simultaneously
-
-The extension handles all async complexity internally using a macro-based architecture.
-
-## ๐ฎ Player Management
-
-### Player Join/Leave Tracking
+## Status Check
```sqf
-// When player joins
-_playerUID = getPlayerUID player;
-_playerName = name player;
-
-// Store player info in hash
-"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "name", _playerName]];
-"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "join_time", str time]];
-
-// Add to online players set
-"forge_server" callExtension ["redis:set:add", ["online_players", _playerUID]];
-
-// When player leaves
-"forge_server" callExtension ["redis:set:del", ["online_players", _playerUID]];
-"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "leave_time", str time]];
-```
-
-### Player Statistics System
-
-```sqf
-// Initialize player stats
-fnc_initPlayerStats = {
- params ["_playerUID"];
-
- _playerKey = format ["stats:%1", _playerUID];
- "forge_server" callExtension ["redis:hash:mset", [_playerKey, [
- ["kills", "0"],
- ["deaths", "0"],
- ["score", "0"],
- ["playtime", "0"]
- ]]];
-};
-
-// Update player kill
-fnc_addPlayerKill = {
- params ["_playerUID"];
-
- _playerKey = format ["stats:%1", _playerUID];
- "forge_server" callExtension ["redis:hash:incr", [_playerKey, "kills", 1]];
- "forge_server" callExtension ["redis:hash:incr", [_playerKey, "score", 10]];
-};
-
-// Get player stats (raw response)
-fnc_getPlayerStats = {
- params ["_playerUID"];
-
- _playerKey = format ["stats:%1", _playerUID];
- _rawResult = "forge_server" callExtension ["redis:hash:getall", [_playerKey]];
- // _rawResult is now "kills,15,deaths,3,score,150,playtime,7200"
-
- // Game modules would parse this into structured data
- // For now, return raw comma-separated response
- _rawResult select 0;
+["status", []] call forge_server_extension_fnc_extCall params ["_status", "_ok"];
+if (_ok && {_status isEqualTo "connected"}) then {
+ systemChat "Forge persistence is online.";
};
```
-## ๐ Leaderboards and Rankings
-
-### Global Kill Leaderboard
+## Actor Fetch
```sqf
-// Add score to sorted leaderboard (using list for simplicity)
-fnc_updateLeaderboard = {
- params ["_playerName", "_kills"];
-
- // Store individual score
- "forge_server" callExtension ["redis:common:set", [format ["kills:%1", _playerName], str _kills]];
-
- // Add to leaderboard tracking
- "forge_server" callExtension ["redis:set:add", ["leaderboard_players", _playerName]];
-};
-
-// Get top 10 players (raw response handling)
-fnc_getTopPlayers = {
- // Get all leaderboard players - returns comma-separated list
- _playersResult = "forge_server" callExtension ["redis:set:members", ["leaderboard_players"]];
- _rawPlayers = _playersResult select 0;
-
- // Check for error
- if (_rawPlayers find "Error:" == 0) exitWith { [] };
-
- // Split comma-separated player list
- _players = _rawPlayers splitString ",";
- _scoreArray = [];
-
- // Get scores for all players
- {
- _killsResult = "forge_server" callExtension ["redis:common:get", [format ["kills:%1", _x]]];
- _rawKills = _killsResult select 0;
-
- // Check for valid response (not an error)
- if (_rawKills find "Error:" != 0) then {
- _scoreArray pushBack [_x, parseNumber _rawKills];
- };
- } forEach _players;
-
- // Sort by score (highest first)
- _scoreArray sort false;
- _scoreArray resize (10 min (count _scoreArray)); // Top 10
-
- _scoreArray;
+private _uid = getPlayerUID player;
+["actor:get", [_uid]] call forge_server_extension_fnc_extCall params ["_payload", "_ok"];
+if (_ok) then {
+ private _actor = fromJSON _payload;
+ systemChat format ["Loaded actor %1", _actor getOrDefault ["uid", _uid]];
};
```
-## ๐ฏ Mission State Management
-
-### Objective System
+## Store Checkout
```sqf
-// Set mission objectives
-fnc_initMissionObjectives = {
- "forge_server" callExtension ["redis:list:rpush", ["objectives", "Secure Alpha Base"]];
- "forge_server" callExtension ["redis:list:rpush", ["objectives", "Extract Intel"]];
- "forge_server" callExtension ["redis:list:rpush", ["objectives", "Eliminate HVT"]];
-
- // Set current objective pointer
- "forge_server" callExtension ["redis:common:set", ["current_objective", "0"]];
-};
-
-// Complete current objective
-fnc_completeObjective = {
- // Get current objective index - returns raw string
- _indexResult = "forge_server" callExtension ["redis:common:get", ["current_objective"]];
- _rawIndex = _indexResult select 0;
-
- // Check for error
- if (_rawIndex find "Error:" == 0) exitWith {};
-
- _currentIndex = parseNumber _rawIndex;
-
- // Get objective name - returns raw string
- _objResult = "forge_server" callExtension ["redis:list:get", ["objectives", _currentIndex]];
- _objectiveName = _objResult select 0;
-
- // Check for valid response
- if (_objectiveName find "Error:" != 0) then {
- // Move to completed objectives - returns new list length
- "forge_server" callExtension ["redis:list:rpush", ["completed_objectives", _objectiveName]];
-
- // Move to next objective - returns "OK"
- "forge_server" callExtension ["redis:common:set", ["current_objective", str (_currentIndex + 1)]];
-
- // Broadcast completion
- [format ["Objective Complete: %1", _objectiveName]] remoteExec ["hint"];
- };
-};
-
-// Get mission progress - raw responses
-fnc_getMissionProgress = {
- _totalResult = "forge_server" callExtension ["redis:list:len", ["objectives"]];
- _completedResult = "forge_server" callExtension ["redis:list:len", ["completed_objectives"]];
-
- _rawTotal = _totalResult select 0;
- _rawCompleted = _completedResult select 0;
-
- // Check for errors
- if (_rawTotal find "Error:" == 0 || _rawCompleted find "Error:" == 0) exitWith {
- "Mission Progress: Unknown";
- };
-
- _total = parseNumber _rawTotal;
- _completed = parseNumber _rawCompleted;
-
- format ["Mission Progress: %1/%2 objectives completed", _completed, _total];
-};
-```
-
-## ๐ Vehicle and Equipment Tracking
-
-### Vehicle Pool System
-
-```sqf
-// Initialize vehicle pool
-fnc_initVehiclePool = {
- params ["_vehicleClass", "_count"];
-
- for "_i" from 1 to _count do {
- _vehicleId = format ["%1_%2", _vehicleClass, _i];
- "forge_server" callExtension ["redis:set:add", ["available_vehicles", _vehicleId]];
- "forge_server" callExtension ["redis:hash:mset", [format ["vehicle:%1", _vehicleId], [
- ["class", _vehicleClass],
- ["status", "available"],
- ["condition", "100"]
- ]]];
- };
-};
-
-// Request vehicle
-fnc_requestVehicle = {
- params ["_playerUID"];
-
- // Get random available vehicle
- _result = "forge_server" callExtension ["redis:set:pop", ["available_vehicles"]];
- _data = fromJSON (_result select 0);
-
- if ((_data select "status") == "success") then {
- _vehicleId = _data select "data";
-
- // Mark as in use
- "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "in_use"]];
- "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "user", _playerUID]];
- "forge_server" callExtension ["redis:set:add", ["used_vehicles", _vehicleId]];
-
- _vehicleId;
- } else {
- ""; // No vehicles available
- };
-};
-
-// Return vehicle
-fnc_returnVehicle = {
- params ["_vehicleId", "_condition"];
-
- // Update condition
- "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "condition", str _condition]];
-
- // Return to pool if condition is good
- if (_condition > 50) then {
- "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "available"]];
- "forge_server" callExtension ["redis:hash:del", [format ["vehicle:%1", _vehicleId], "user"]];
- "forge_server" callExtension ["redis:set:del", ["used_vehicles", _vehicleId]];
- "forge_server" callExtension ["redis:set:add", ["available_vehicles", _vehicleId]];
- } else {
- "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "maintenance"]];
- "forge_server" callExtension ["redis:set:add", ["maintenance_vehicles", _vehicleId]];
- };
-};
-```
-
-## ๐ Server Analytics
-
-### Player Session Tracking
-
-```sqf
-// Track player session start
-fnc_startPlayerSession = {
- params ["_playerUID"];
-
- _sessionId = format ["%1_%2", _playerUID, floor time];
- _sessionKey = format ["session:%1", _sessionId];
-
- "forge_server" callExtension ["redis:hash:mset", [_sessionKey, [
- ["player_uid", _playerUID],
- ["start_time", str time],
- ["server_id", serverName],
- ["player_count", str (count allPlayers)]
- ]]];
-
- // Store current session for player
- "forge_server" callExtension ["redis:common:set", [format ["current_session:%1", _playerUID], _sessionId]];
-
- _sessionId;
-};
-
-// End player session
-fnc_endPlayerSession = {
- params ["_playerUID", "_sessionStats"];
-
- // Get current session
- _result = "forge_server" callExtension ["redis:common:get", [format ["current_session:%1", _playerUID]]];
- _data = fromJSON (_result select 0);
-
- if ((_data select "status") == "success") then {
- _sessionId = _data select "data";
- _sessionKey = format ["session:%1", _sessionId];
-
- // Update session with end data
- "forge_server" callExtension ["redis:hash:mset", [_sessionKey, [
- ["end_time", str time],
- ["duration", str (_sessionStats select "duration")],
- ["kills", str (_sessionStats select "kills")],
- ["deaths", str (_sessionStats select "deaths")]
- ]]];
-
- // Clean up current session tracking
- "forge_server" callExtension ["redis:common:del", [format ["current_session:%1", _playerUID]]];
- };
-};
-```
-
-## ๐ Cross-Server Communication
-
-### Message Queue System
-
-```sqf
-// Send message to other servers
-fnc_sendCrossServerMessage = {
- params ["_targetServer", "_messageType", "_messageData"];
-
- _message = createHashMap;
- _message set ["from_server", serverName];
- _message set ["type", _messageType];
- _message set ["data", _messageData];
- _message set ["timestamp", str time];
-
- _queueKey = format ["messages:%1", _targetServer];
- "forge_server" callExtension ["redis:list:rpush", [_queueKey, str _message]];
-};
-
-// Check for incoming messages
-fnc_checkMessages = {
- _queueKey = format ["messages:%1", serverName];
-
- // Get next message
- _result = "forge_server" callExtension ["redis:list:lpop", [_queueKey, 1]];
- _data = fromJSON (_result select 0);
-
- if ((_data select "status") == "success") then {
- _messages = _data select "data";
- if (count _messages > 0) then {
- _messageStr = _messages select 0;
- _message = fromJSON _messageStr;
-
- // Process message based on type
- _type = _message select "type";
- _messageData = _message select "data";
-
- switch (_type) do {
- case "player_transfer": {
- [_messageData] call fnc_handlePlayerTransfer;
- };
- case "server_status": {
- [_messageData] call fnc_handleServerStatus;
- };
- case "admin_broadcast": {
- [_messageData select "message"] remoteExec ["hint"];
- };
- };
- };
- };
-};
-
-// Run message checker periodically
-[] spawn {
- while {true} do {
- call fnc_checkMessages;
- sleep 5; // Check every 5 seconds
- };
-};
-```
-
-## ๐ ๏ธ Utility Functions
-
-### Redis Helper Functions
-
-```sqf
-// Parse Redis response safely
-fnc_parseRedisResponse = {
- params ["_response"];
-
- try {
- _data = fromJSON (_response select 0);
- if ((_data select "status") == "success") then {
- _data select "data";
- } else {
- diag_log format ["Redis Error: %1", _data select "error"];
- nil;
- };
- } catch {
- diag_log format ["JSON Parse Error: %1", _exception];
- nil;
- };
-};
-
-// Batch Redis operations
-fnc_redisBatch = {
- params ["_operations"];
-
- _results = [];
- {
- _op = _x;
- _result = "forge_server" callExtension [_op select 0, _op select 1];
- _results pushBack (fromJSON (_result select 0));
- } forEach _operations;
-
- _results;
-};
-
-// Example batch usage:
-_batchOps = [
- ["redis:common:set", ["key1", "value1"]],
- ["redis:common:set", ["key2", "value2"]],
- ["redis:common:get", ["key1"]]
+private _checkout = createHashMapFromArray [
+ ["requesterUid", getPlayerUID player],
+ ["requesterName", name player],
+ ["orgId", "default"],
+ ["requesterIsDefaultOrgCeo", false],
+ ["paymentMethod", "bank"],
+ ["items", [
+ createHashMapFromArray [
+ ["classname", "FirstAidKit"],
+ ["category", "item"],
+ ["priceValue", 50],
+ ["quantity", 2]
+ ]
+ ]],
+ ["vehicles", []]
];
-_results = [_batchOps] call fnc_redisBatch;
+
+["store:checkout", [toJSON _checkout]] call forge_server_extension_fnc_extCall;
```
-
-## ๐ฏ Best Practices
-
-### Error Handling Pattern
-
-```sqf
-fnc_safeRedisCall = {
- params ["_command", "_params", ["_defaultValue", nil]];
-
- try {
- _result = "forge_server" callExtension [_command, _params];
- _data = fromJSON (_result select 0);
-
- if ((_data select "status") == "success") then {
- _data select "data";
- } else {
- diag_log format ["Redis operation failed: %1 - %2", _command, _data select "error"];
- _defaultValue;
- };
- } catch {
- diag_log format ["Redis call exception: %1 - %2", _command, _exception];
- _defaultValue;
- };
-};
-
-// Usage:
-_playerName = ["redis:common:get", ["player_name"], "Unknown"] call fnc_safeRedisCall;
-```
-
-These examples demonstrate real-world usage patterns for the Redis extension in Arma 3 environments, covering player management, mission state, analytics, and cross-server communication.
diff --git a/arma/server/extension/Cargo.toml b/arma/server/extension/Cargo.toml
index e4410d1..dd91c69 100644
--- a/arma/server/extension/Cargo.toml
+++ b/arma/server/extension/Cargo.toml
@@ -10,14 +10,12 @@ crate-type = ["cdylib"]
[dependencies]
arma-rs = { workspace = true }
base64 = "0.22.1"
-bb8-redis = "0.26.0"
chrono = { workspace = true }
forge-icom = { path = "../../../bin/icom" }
forge-models = { path = "../../../lib/models", features = ["actor"] }
forge-repositories = { path = "../../../lib/repositories" }
forge-services = { path = "../../../lib/services" }
forge-shared = { path = "../../../lib/shared" }
-redis = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
surrealdb = { version = "2", default-features = false, features = ["protocol-http", "rustls"] }
diff --git a/arma/server/extension/README.md b/arma/server/extension/README.md
index 7f118a4..128af85 100644
--- a/arma/server/extension/README.md
+++ b/arma/server/extension/README.md
@@ -1,405 +1,41 @@
-# Forge Arma 3 Server Extension
+# Forge Server Extension
-This extension provides the core server-side functionality for the Forge framework, handling persistent data storage, actor management, and game state synchronization through a high-performance Rust backend.
+The Forge server extension is the Rust backend for server-side game systems.
+It exposes domain commands through `arma-rs`, runs a shared Tokio runtime, and
+persists durable state through SurrealDB.
-## Architecture
+## Responsibilities
-The extension follows a layered architecture designed for reliability, performance, and maintainability:
+- Register extension command groups for actor, bank, garage, locker, org,
+ phone, store, task, CAD, terrain, and transport systems.
+- Load extension configuration from `@forge_server/config.toml`.
+- Connect to SurrealDB and apply schema modules on startup.
+- Keep SQF-facing command handlers thin while service crates own domain rules.
-- **Extension Layer**: Handles the raw Arma 3 `callExtension` interface, parameter parsing, and command routing.
-- **Service Layer**: Implements business logic, validation, and orchestration of operations (e.g., `ActorService`).
-- **Repository Layer**: Manages data persistence and retrieval using Redis (e.g., `RedisActorRepository`).
-- **Model Layer**: Defines strict data structures and validation rules (e.g., `Actor` model).
+## Configuration
-This separation ensures that game logic is decoupled from data storage and that all data entering the system is validated before persistence.
-
-### Module Documentation
-
-For detailed information about specific modules, see:
-
-- **[Redis Operations](src/redis/README.md)**: Comprehensive guide to Redis commands (hash, list, set, common operations)
-- **[Adapters](src/adapters/README.md)**: Adapter pattern implementation bridging repositories with Redis
-
-## Organization Management
-
-The Organization module handles guild/clan management, allowing players to form groups, manage members, and persist organizational data. It supports role management, automatic UID resolution, and robust error handling.
-
-### Available Commands
-
-| Command | Description |
-| ------------------- | ------------------------------------------------------- |
-| `org:get` | Retrieve organization data by key or ID. |
-| `org:create` | Create a new organization with provided JSON data. |
-| `org:update` | Update an existing organization with partial JSON data. |
-| `org:delete` | Permanently remove an organization and its data. |
-| `org:exists` | Check if an organization exists. |
-| `org:get_members` | Retrieve a list of organization members. |
-| `org:add_member` | Add a member to an organization. |
-| `org:remove_member` | Remove a member from an organization. |
-
-### SQF Examples
-
-#### Retrieving an Organization
-
-```sqf
-// Get organization by ID
-private _result = "forge_server" callExtension ["org:get", ["elite_squad"]];
-private _orgData = fromJSON (_result select 0);
-
-// Access data
-private _name = _orgData get "name";
-private _leader = _orgData get "leader";
+```toml
+[surreal]
+endpoint = "127.0.0.1:8000"
+namespace = "forge"
+database = "main"
+username = "root"
+password = "root"
+connect_timeout_ms = 5000
```
-#### Creating an Organization
+## Status
```sqf
-// Prepare data using HashMap
-private _data = createHashMapFromArray [
- ["name", "Elite Squad"],
- ["description", "Best players"],
- ["leader", getPlayerUID player],
- ["max_members", 50],
- ["type", "military"]
-];
-
-// Create the organization
-private _result = "forge_server" callExtension ["org:create", ["elite_squad", toJSON _data]];
-
-if ((_result select 0) find "Error:" == 0) then {
- diag_log format ["Failed to create org: %1", _result select 0];
-} else {
- private _createdOrg = fromJSON (_result select 0);
- systemChat format ["Created organization: %1", _createdOrg get "name"];
-};
+"forge_server" callExtension ["status", []];
+"forge_server" callExtension ["surreal:status", []];
```
-#### Updating an Organization
+Status values are `initializing`, `connected`, or `failed`.
-```sqf
-// Prepare partial update
-private _update = createHashMapFromArray [
- ["description", "Updated description"],
- ["max_members", 100]
-];
+## Build
-// Apply update
-private _result = "forge_server" callExtension ["org:update", ["elite_squad", toJSON _update]];
+```powershell
+cargo test -p forge-server
+cargo build -p forge-server
```
-
-#### Managing Members
-
-```sqf
-// Get members
-private _result = "forge_server" callExtension ["org:get_members", ["elite_squad"]];
-private _members = fromJSON (_result select 0);
-
-// Add a member
-private _addResult = "forge_server" callExtension ["org:add_member", ["elite_squad", "76561198123456789"]];
-
-// Remove a member
-private _removeResult = "forge_server" callExtension ["org:remove_member", ["elite_squad", "76561198123456789"]];
-```
-
-#### Checking Existence
-
-```sqf
-private _exists = "forge_server" callExtension ["org:exists", ["elite_squad"]];
-
-if ((_exists select 0) == "true") then {
- systemChat "Organization exists.";
-};
-```
-
-#### Deleting an Organization
-
-```sqf
-// Permanently delete organization
-private _result = "forge_server" callExtension ["org:delete", ["elite_squad"]];
-
-if ((_result select 0) == "OK") then {
- systemChat "Organization deleted.";
-};
-```
-
-## Actor Management
-
-The Actor module handles all player-related operations, including data retrieval, creation, updates, and existence checks. It features automatic Steam UID resolution and robust error handling.
-
-### Available Commands
-
-| Command | Description |
-| -------------- | ------------------------------------------------ |
-| `actor:get` | Retrieve actor data by key or UID. |
-| `actor:create` | Create a new actor with provided JSON data. |
-| `actor:update` | Update an existing actor with partial JSON data. |
-| `actor:exists` | Check if an actor exists in the database. |
-| `actor:delete` | Permanently remove an actor and their data. |
-
-### SQF Examples
-
-The extension is designed to work seamlessly with modern Arma 3 SQF features like `HashMap` and `toJSON`/`fromJSON`.
-
-#### Retrieving an Actor
-
-```sqf
-// Get actor by Steam UID
-private _result = "forge_server" callExtension ["actor:get", ["76561198123456789"]];
-private _actorData = fromJSON (_result select 0);
-
-// Access data
-private _name = _actorData get "name";
-private _bank = _actorData get "bank";
-```
-
-#### Creating an Actor
-
-```sqf
-// Prepare data using HashMap
-private _data = createHashMapFromArray [
- ["name", "John Doe"],
- ["bank", 1000],
- ["cash", 100],
- ["level", 1],
- ["class", "civilian"]
-];
-
-// Create the actor
-private _result = "forge_server" callExtension ["actor:create", ["player123", toJSON _data]];
-
-if ((_result select 0) find "Error:" == 0) then {
- diag_log format ["Failed to create actor: %1", _result select 0];
-} else {
- private _createdActor = fromJSON (_result select 0);
- systemChat format ["Welcome, %1!", _createdActor get "name"];
-};
-```
-
-#### Updating an Actor
-
-```sqf
-// Prepare partial update
-private _update = createHashMapFromArray [
- ["bank", 1500],
- ["level", 2]
-];
-
-// Apply update
-private _result = "forge_server" callExtension ["actor:update", ["player123", toJSON _update]];
-```
-
-#### Checking Existence
-
-```sqf
-private _exists = "forge_server" callExtension ["actor:exists", ["player123"]];
-
-if ((_exists select 0) == "true") then {
- systemChat "Player profile found.";
-} else {
- systemChat "Player profile not found.";
-};
-```
-
-#### Deleting an Actor
-
-```sqf
-// Permanently delete actor data
-private _result = "forge_server" callExtension ["actor:delete", ["player123"]];
-
-if ((_result select 0) == "OK") then {
- systemChat "Actor deleted successfully.";
-};
-```
-
-## Error Handling
-
-The extension uses a consistent error reporting format. If an operation fails, the returned string will start with `Error: ` followed by a descriptive message.
-
-- **Consistent Responses**: All commands return JSON on success or an error message on failure.
-- **No Fallbacks**: `actor:get` and `org:get` will return error messages if the requested entity cannot be found, rather than fallback objects with dummy data.
-- **Validation**: All input data is validated against the strict schema defined in the models. Invalid data will result in an error message.
-
-### Example Error Handling
-
-```sqf
-private _result = "forge_server" callExtension ["actor:get", ["76561198123456789"]];
-private _response = _result select 0;
-
-if (_response find "Error:" == 0) then {
- diag_log format ["Failed to get actor: %1", _response];
-} else {
- private _actorData = fromJSON _response;
- systemChat format ["Welcome, %1!", _actorData get "name"];
-};
-```
-
-## Performance
-
-- **Asynchronous Core**: Built on `tokio`, the extension performs heavy I/O operations (like database writes) without blocking the Arma 3 simulation thread.
-- **Connection Pooling**: Uses a Redis connection pool to efficiently manage database connections.
-- **Lazy Initialization**: Services are initialized only when first needed, reducing startup time.
-- **Minimal Serialization**: Only necessary data is serialized and transferred between Rust and SQF to minimize overhead.
-
-## Contributing
-
-We welcome contributions to the Forge Extension! This guide will help you understand how to add new commands and maintain the existing codebase.
-
-### Adding a Command to an Existing Module
-
-To add a new command to an existing module (e.g., `actor:set_position`), follow these steps:
-
-1. **Register the Command**: In the module file (e.g., `src/actor.rs`), add the command to the `group()` function.
-
- ```rust
- pub fn group() -> Group {
- Group::new()
- .command("get", get_actor)
- .command("exists", exists_actor)
- .command("create", create_actor)
- .command("update", update_actor)
- .command("delete", delete_actor)
- .command("set_position", set_actor_position) // New command
- }
- ```
-
-2. **Implement the Handler Function**: Create the function that handles the command logic.
-
- ```rust
- use crate::log::log;
-
- /// Sets the position of an actor.
- pub fn set_actor_position(call_context: CallContext, key: String, position: String) -> String {
- log("actor", "DEBUG", &format!("Setting position for key: {}", key));
-
- // 1. Resolve UID
- let resolved_uid = match resolve_uid(&key, &call_context) {
- Some(uid) => uid,
- None => {
- let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
- log("actor", "ERROR", &error_msg);
- return error_msg;
- }
- };
-
- // 2. Parse and validate input
- let position_data: Vec = match serde_json::from_str(&position) {
- Ok(data) => data,
- Err(e) => {
- let error_msg = format!("Error: Invalid position JSON: {}", e);
- log("actor", "ERROR", &error_msg);
- return error_msg;
- }
- };
-
- // 3. Get the actor, update position, and save
- match ACTOR_SERVICE.get_actor(resolved_uid.clone()) {
- Ok(mut actor) => {
- actor.set_position(position_data);
-
- match ACTOR_SERVICE.update_actor(actor.clone()) {
- Ok(_) => {
- log("actor", "INFO", &format!("Updated position for: {}", resolved_uid));
- match serde_json::to_string(&actor) {
- Ok(json) => json,
- Err(e) => format!("Error: Failed to serialize actor: {}", e),
- }
- }
- Err(e) => format!("Error: {}", e),
- }
- }
- Err(e) => format!("Error: {}", e),
- }
- }
- ```
-
-### Creating a New Module
-
-To create a new module (e.g., `vehicle`), follow these steps:
-
-1. **Create the Module File**: Add `src/vehicle.rs`.
-2. **Create the Global Service Instance**: Define a lazily initialized singleton service.
-
- ```rust
- use std::sync::LazyLock;
- use forge_services::VehicleService;
- use forge_repositories::RedisVehicleRepository;
- use crate::adapters::ExtensionRedisClient;
-
- static VEHICLE_SERVICE: LazyLock>> =
- LazyLock::new(|| {
- let redis_client = ExtensionRedisClient::new();
- let repository = RedisVehicleRepository::new(redis_client);
- VehicleService::new(repository)
- });
- ```
-
-3. **Register the Command**: In the module file, register the command in the `group()` function.
- ```rust
- pub fn group() -> Group {
- Group::new()
- .command("get", get_vehicle)
- .command("create", create_vehicle)
- // ... other commands
- }
- ```
-4. **Use Logging**: Import and use the generic `log` function in your handler functions.
-
- ```rust
- use crate::log::log;
-
- pub fn get_vehicle(key: String) -> String {
- log("vehicle", "DEBUG", &format!("Getting vehicle for key: {}", key));
-
- // Call service layer
- match VEHICLE_SERVICE.get_vehicle(key.clone()) {
- Ok(vehicle) => {
- log("vehicle", "INFO", &format!("Successfully retrieved vehicle: {}", key));
- match serde_json::to_string(&vehicle) {
- Ok(json) => {
- log("vehicle", "DEBUG", &format!("Serialized vehicle to JSON: {}", json));
- json
- }
- Err(e) => {
- let error_msg = format!("Error: Failed to serialize vehicle: {}", e);
- log("vehicle", "ERROR", &error_msg);
- error_msg
- }
- }
- }
- Err(e) => {
- let error_msg = format!("Error: {}", e);
- log("vehicle", "ERROR", &format!("Failed to get vehicle '{}': {}", key, e));
- error_msg
- }
- }
- }
- ```
-
- The `log` function takes three parameters:
- - `category`: The log category (e.g., "vehicle", "actor", "org")
- - `level`: The log level ("INFO", "DEBUG", "WARN", "ERROR")
- - `message`: The message to log
-
- Log files are created automatically in `@forge_server/logs/{category}.log`.
-
-5. **Register the Module** (if new): If you created a new module, add it to `src/lib.rs`.
-
- ```rust
- pub mod vehicle;
-
- // In the extension function, register the group
- extension.group("vehicle", vehicle::group());
- ```
-
-### Testing
-
-- **In-Game Testing**: Test your commands in Arma 3 to ensure they work correctly with SQF.
-- **Error Cases**: Test error scenarios (invalid input, missing entities, etc.) to ensure proper error messages.
-
-### Best Practices
-
-- **Return Types**: Always return `String` (JSON on success, error message on failure).
-- **Error Messages**: Prefix all error messages with `"Error: "` for consistency.
-- **Logging**: Use the `log(category, level, message)` function to track operations.
-- **Service Layer**: Delegate business logic to the service layer. The extension layer should only handle parameter parsing and response formatting.
-- **Validation**: Validate inputs before calling the service layer to provide clear error messages.
diff --git a/arma/server/extension/config.example.toml b/arma/server/extension/config.example.toml
index f2df52b..bd1bc63 100644
--- a/arma/server/extension/config.example.toml
+++ b/arma/server/extension/config.example.toml
@@ -1,59 +1,14 @@
-# Crate Server Configuration
-# Copy this file to config.toml and modify as needed
-# Place this file in the same directory as your crate_server_x64.dll
-
-[storage]
-# Redis remains the default while modules are migrated incrementally.
-# Current SurrealDB-backed durable repositories:
-# actor, bank, garage, locker, owned garage, owned locker, org, phone.
-backend = "redis" # "redis" or "surreal"
-
-[redis]
-# Redis server connection settings
-host = "127.0.0.1"
-port = 6379
-db = 0 # Redis database number (0-15)
-
-# Optional authentication
-# username = "your_username"
-# password = "your_password"
-
-# Optional connection pool settings
-max_connections = 10 # Maximum number of connections in pool
-min_connections = 2 # Minimum number of idle connections
-idle_timeout = 60 # Idle connection timeout in seconds
-connect_timeout_ms = 2000 # Pool connect timeout in milliseconds
-pool_get_timeout_ms = 2000 # Pool checkout timeout in milliseconds
-command_timeout_ms = 2000 # Redis command timeout in milliseconds
+# Forge Server Configuration
+# Copy this file to config.toml and place it beside forge_server_x64.dll.
[surreal]
-# SurrealDB HTTP endpoint. Use "127.0.0.1:8000" for a local SurrealDB server.
+# SurrealDB HTTP endpoint. Use "127.0.0.1:8000" for a local server.
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
-# Optional authentication
+# Optional authentication.
username = "root"
password = "root"
connect_timeout_ms = 5000
-
-# Example configurations for different environments:
-
-# Development (local Redis)
-# host = "127.0.0.1"
-# port = 6379
-# max_connections = 5
-# min_connections = 1
-
-# Production (remote Redis with auth)
-# host = "redis.example.com"
-# port = 6379
-# username = "arma_server"
-# password = "secure_password_here"
-# max_connections = 20
-# min_connections = 5
-# idle_timeout = 30
-# connect_timeout_ms = 5000
-# pool_get_timeout_ms = 5000
-# command_timeout_ms = 5000
diff --git a/arma/server/extension/src/actor.rs b/arma/server/extension/src/actor.rs
index 7565c59..59672d0 100644
--- a/arma/server/extension/src/actor.rs
+++ b/arma/server/extension/src/actor.rs
@@ -15,7 +15,7 @@ use crate::storage::ActorStorageRepository;
/// Global actor service instance.
///
-/// Lazily initialized singleton combining Redis adapter, repository, and service layers.
+/// Lazily initialized singleton combining repository and service layers.
static ACTOR_SERVICE: LazyLock> =
LazyLock::new(|| ActorService::new(ActorStorageRepository::configured()));
static HOT_ACTOR_SERVICE: LazyLock<
diff --git a/arma/server/extension/src/adapters/README.md b/arma/server/extension/src/adapters/README.md
deleted file mode 100644
index 167b2bd..0000000
--- a/arma/server/extension/src/adapters/README.md
+++ /dev/null
@@ -1,270 +0,0 @@
-# Adapters Module
-
-This module provides adapter implementations that bridge the repository layer with the extension's Redis operations. Adapters translate between the generic `RedisClient` trait and the extension-specific Redis module.
-
-## Architecture
-
-The adapters module follows the **Adapter Pattern**, allowing the repository layer to remain decoupled from the specific Redis implementation:
-
-```mermaid
-graph TD
- Repo[Repository Layer
#40;forge-repositories#41;]
- Trait[RedisClient Trait
#40;forge-shared#41;]
- Adapter[ExtensionRedisClient
#40;adapter#41;]
- Redis[Redis Module
#40;extension#41;]
-
- Repo --> Trait
- Trait --> Adapter
- Adapter --> Redis
-```
-
-This design enables:
-
-- **Testability**: Repositories can use mock adapters for testing
-- **Flexibility**: Different Redis implementations can be swapped without changing repositories
-- **Separation of Concerns**: Repository logic is independent of Redis connection details
-
-## ExtensionRedisClient
-
-The `ExtensionRedisClient` is the primary adapter that implements the `RedisClient` trait from `forge_shared`.
-
-### Responsibilities
-
-- **Translate Calls**: Convert trait method calls to Redis module function calls
-- **Error Handling**: Parse Redis operation results and convert to `Result` types
-- **Data Transformation**: Handle response parsing (e.g., JSON arrays for lists/sets)
-- **Logging**: Log debug information for Redis operations
-
-### Implemented Operations
-
-#### Hash Operations
-
-| Method | Description | Returns |
-| -------------- | ------------------------------ | ------------------------ |
-| `hash_mset` | Set multiple fields atomically | `Result<(), String>` |
-| `hash_get_all` | Get all fields and values | `Result` |
-| `hash_get` | Get a single field value | `Result` |
-| `hash_del` | Delete a field | `Result<(), String>` |
-
-#### List Operations
-
-| Method | Description | Returns |
-| ------------ | --------------------- | ----------------------------- |
-| `list_rpush` | Append to list | `Result<(), String>` |
-| `list_range` | Get range of elements | `Result, String>` |
-| `list_del` | Remove by value | `Result<(), String>` |
-
-#### Set Operations
-
-| Method | Description | Returns |
-| ------------- | --------------- | ----------------------------- |
-| `set_add` | Add member | `Result<(), String>` |
-| `set_members` | Get all members | `Result, String>` |
-| `set_del` | Remove member | `Result<(), String>` |
-
-#### Common Operations
-
-| Method | Description | Returns |
-| ------------ | ------------------- | ---------------------- |
-| `key_exists` | Check if key exists | `Result` |
-| `delete_key` | Delete key | `Result<(), String>` |
-
-### Usage Example
-
-```rust
-use crate::adapters::ExtensionRedisClient;
-use forge_shared::RedisClient;
-
-// Create the adapter
-let client = ExtensionRedisClient::new();
-
-// Use it with the RedisClient trait
-let fields = vec![
- ("name".to_string(), "John".to_string()),
- ("age".to_string(), "30".to_string()),
-];
-client.hash_mset("user:123".to_string(), fields)?;
-
-// Retrieve data
-let data = client.hash_get_all("user:123".to_string())?;
-```
-
-## Error Handling
-
-The adapter translates Redis string responses to Rust `Result` types:
-
-- **Success**: Returns `Ok(value)` with the appropriate type
-- **Error**: Returns `Err(message)` if the response starts with "Error:"
-
-```rust
-// Redis module returns "OK" โ Adapter returns Ok(())
-// Redis module returns "Error: Connection failed" โ Adapter returns Err("Error: Connection failed")
-```
-
-### Response Parsing
-
-For operations that return collections, the adapter parses JSON responses:
-
-```rust
-// list_range returns JSON: ["item1", "item2", "item3"]
-let items = client.list_range("mylist".to_string(), 0, -1)?;
-// items: Vec = vec!["item1", "item2", "item3"]
-```
-
-## Contributing
-
-We welcome contributions to the adapters module! Follow these guidelines to add new adapter methods or create new adapters.
-
-### Adding a New Method to ExtensionRedisClient
-
-To add a new method (e.g., `hash_exists`), follow these steps:
-
-1. **Check the Trait**: Ensure the method is defined in the `RedisClient` trait in `forge_shared`.
-
- ```rust
- // In forge_shared/src/redis_client.rs
- pub trait RedisClient: Send + Sync {
- fn hash_exists(&self, key: String, field: String) -> Result;
- }
- ```
-
-2. **Implement the Method**: Add the implementation to `ExtensionRedisClient`.
-
- ```rust
- impl RedisClient for ExtensionRedisClient {
- fn hash_exists(&self, key: String, field: String) -> Result {
- // Call the Redis module function
- let result = redis::hash::hash_exists(key, field);
-
- // Parse the response
- match result.as_str() {
- "1" => Ok(true),
- "0" => Ok(false),
- _ if result.starts_with("Error:") => Err(result),
- _ => Err(format!("Unexpected response: {}", result)),
- }
- }
- }
- ```
-
-3. **Add Logging** (if needed): For debugging, log the operation.
-
- ```rust
- fn hash_exists(&self, key: String, field: String) -> Result {
- let result = redis::hash::hash_exists(key, field);
- log("debug", "DEBUG", &format!("hash_exists({}, {}): {}", key, field, result));
-
- match result.as_str() {
- "1" => Ok(true),
- "0" => Ok(false),
- _ if result.starts_with("Error:") => Err(result),
- _ => Err(format!("Unexpected response: {}", result)),
- }
- }
- ```
-
-4. **Handle Response Types**: Match the return type to the trait signature.
- - **Unit type** (`()`): Return `Ok(())` on success
- - **Boolean**: Parse "1"/"0" to `true`/`false`
- - **String**: Return the value directly
- - **Vec**: Parse JSON array response
- - **Number**: Parse string to number
-
-### Creating a New Adapter
-
-To create a new adapter (e.g., `MockRedisClient` for testing):
-
-1. **Create the Module File**: Add `src/adapters/mock_client.rs`.
-2. **Define the Struct**: Create the adapter struct.
-
- ```rust
- use forge_shared::RedisClient;
- use std::collections::HashMap;
- use std::sync::RwLock;
-
- /// Mock Redis client for testing.
- ///
- /// Uses RwLock to allow multiple concurrent readers while maintaining thread safety.
- pub struct MockRedisClient {
- data: RwLock>,
- }
-
- impl MockRedisClient {
- pub fn new() -> Self {
- Self {
- data: RwLock::new(HashMap::new()),
- }
- }
- }
- ```
-
-3. **Implement the Trait**: Implement all `RedisClient` methods.
-
- ```rust
- impl RedisClient for MockRedisClient {
- fn hash_mset(&self, key: String, fields: Vec<(String, String)>) -> Result<(), String> {
- // Acquire write lock only when modifying data
- let mut data = self.data.write().unwrap();
- for (field, value) in fields {
- let hash_key = format!("{}:{}", key, field);
- data.insert(hash_key, value);
- }
- Ok(())
- }
-
- fn hash_get(&self, key: String, field: String) -> Result {
- // Acquire read lock - multiple threads can read concurrently
- let data = self.data.read().unwrap();
- let hash_key = format!("{}:{}", key, field);
- Ok(data.get(&hash_key)
- .map(|v| v.clone())
- .unwrap_or_default())
- }
-
- // ... implement other methods
- }
- ```
-
-4. **Register the Module**: Add to `src/adapters/mod.rs`.
-
- ```rust
- pub mod redis_client;
- pub mod mock_client;
-
- pub use redis_client::ExtensionRedisClient;
- pub use mock_client::MockRedisClient;
- ```
-
-### Concurrency Best Practices
-
-> [!IMPORTANT]
-> Choose the right synchronization primitive based on your access patterns and performance requirements.
-
-**Recommended Synchronization Primitives:**
-
-| Primitive | Use Case | Performance | Dependency |
-| --------------------- | ---------------------------------------- | ----------------------- | ---------------- |
-| **`RwLock`** | Read-heavy workloads, concurrent readers | Good (multiple readers) | Standard library |
-| **`Mutex`** | Write-heavy or exclusive access required | Fair (single lock) | Standard library |
-| **`DashMap`** | Extreme high-frequency reads/writes | Excellent (lock-free) | External crate |
-
-**When to use each:**
-
-- **`RwLock`**: Best for most use cases. Allows multiple concurrent readers, only blocks on writes. Use this by default.
-- **`Mutex`**: Only when you need exclusive access or operations are very lightweight (< 1ฮผs).
-- **`DashMap`**: When profiling shows `RwLock` is a bottleneck and you need lock-free performance.
-
-**Why avoid `Mutex` for read-heavy workloads?**
-
-- Blocks all threads (readers and writers) on every access
-- No concurrent reads possible
-- Can cause performance bottlenecks in high-concurrency scenarios
-
-### Best Practices
-
-- **Error Consistency**: Always check for "Error:" prefix in Redis responses
-- **Type Safety**: Ensure return types match the trait signature exactly
-- **Logging**: Log operations at DEBUG level for troubleshooting
-- **Response Parsing**: Handle all possible response formats (success, error, unexpected)
-- **Documentation**: Document the purpose and behavior of each method
-- **Testing**: Test adapters with both success and error scenarios
diff --git a/arma/server/extension/src/adapters/mod.rs b/arma/server/extension/src/adapters/mod.rs
deleted file mode 100644
index 6fa6c1c..0000000
--- a/arma/server/extension/src/adapters/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod redis_client;
-
-pub use redis_client::ExtensionRedisClient;
diff --git a/arma/server/extension/src/adapters/redis_client.rs b/arma/server/extension/src/adapters/redis_client.rs
deleted file mode 100644
index c9ccf4a..0000000
--- a/arma/server/extension/src/adapters/redis_client.rs
+++ /dev/null
@@ -1,208 +0,0 @@
-use crate::log::log;
-use crate::redis;
-use forge_shared::RedisClient;
-
-/// Redis client implementation that bridges the repository layer with the extension's Redis module.
-pub struct ExtensionRedisClient;
-
-impl ExtensionRedisClient {
- /// Creates a new instance of the Redis client adapter.
- pub fn new() -> Self {
- Self
- }
-}
-
-impl RedisClient for ExtensionRedisClient {
- /// Sets multiple fields in a Redis hash.
- fn hash_mset(&self, key: String, fields: Vec<(String, String)>) -> Result<(), String> {
- let result = redis::hash::hash_mset(key, fields);
- log("debug", "DEBUG", &result);
-
- if result == "OK" { Ok(()) } else { Err(result) }
- }
-
- /// Retrieves all fields and values from a Redis hash.
- fn hash_get_all(&self, key: String) -> Result {
- let result = redis::hash::hash_get_all(key);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(result)
- }
- }
-
- /// Retrieves a single field value from a Redis hash.
- fn hash_get(&self, key: String, field: String) -> Result {
- let result = redis::hash::hash_get(key, field);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(result)
- }
- }
-
- /// Deletes a specific field from a Redis hash.
- fn hash_del(&self, key: String, field: String) -> Result<(), String> {
- let result = redis::hash::hash_del(key, field);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// Appends a value to the end of a Redis list.
- fn list_rpush(&self, key: String, value: String) -> Result<(), String> {
- let result = redis::list::list_rpush(key, value);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// Retrieves a range of elements from a Redis list.
- fn list_range(&self, key: String, start: isize, end: isize) -> Result, String> {
- let result = redis::list::list_range(key, start, end);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- // Parse the JSON array response
- match serde_json::from_str::>(&result) {
- Ok(values) => Ok(values),
- Err(e) => Err(format!("Failed to parse list response: {}", e)),
- }
- }
- }
-
- /// Removes elements from a Redis list by value.
- fn list_del(&self, key: String, count: isize, value: String) -> Result<(), String> {
- let result = redis::list::list_del(key, count, value);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// # Set operations
-
- /// Adds a member to a Redis set.
- fn set_add(&self, key: String, member: String) -> Result<(), String> {
- let result = redis::set::set_add(key, member);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// Retrieves all members from a Redis set.
- fn set_members(&self, key: String) -> Result, String> {
- let result = redis::set::set_members(key);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else if result.trim().is_empty() {
- Ok(Vec::new())
- } else {
- serde_json::from_str::>(&result).or_else(|_| {
- Ok(result
- .split(',')
- .map(str::trim)
- .filter(|value| !value.is_empty())
- .map(ToString::to_string)
- .collect())
- })
- }
- }
-
- /// Removes a member from a Redis set.
- fn set_del(&self, key: String, member: String) -> Result<(), String> {
- let result = redis::set::set_del(key, member);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// Checks if a Redis key exists.
- fn key_exists(&self, key: String) -> Result {
- let result = redis::common::key_exists(key);
- log("debug", "DEBUG", &result);
-
- match result.as_str() {
- "1" => Ok(true),
- "0" => Ok(false),
- _ => Err(format!("Unexpected Redis response: {}", result)),
- }
- }
-
- /// Retrieves the value of a Redis key.
- fn get_key(&self, key: String) -> Result {
- let result = redis::common::get_key(key);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(result)
- }
- }
-
- /// Sets a value in a Redis key.
- fn set_key(&self, key: String, value: String) -> Result<(), String> {
- let result = redis::common::set_key(key, value);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-
- /// Increments a numeric Redis key.
- fn incr_key(&self, key: String, count: usize) -> Result {
- let result = redis::common::incr_key(key, count);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- result
- .parse::()
- .map_err(|error| format!("Failed to parse increment response: {}", error))
- }
- }
-
- /// Deletes a Redis key and all its associated data.
- fn delete_key(&self, key: String) -> Result<(), String> {
- let result = redis::common::delete_key(key);
- log("debug", "DEBUG", &result);
-
- if result.starts_with("Error:") {
- Err(result)
- } else {
- Ok(())
- }
- }
-}
diff --git a/arma/server/extension/src/bank.rs b/arma/server/extension/src/bank.rs
index 0a6f426..8ec899d 100644
--- a/arma/server/extension/src/bank.rs
+++ b/arma/server/extension/src/bank.rs
@@ -19,7 +19,7 @@ use crate::storage::BankStorageRepository;
/// Global bank service instance.
///
-/// Lazily initialized singleton combining Redis adapter, repository, and service layers.
+/// Lazily initialized singleton combining repository and service layers.
static BANK_SERVICE: LazyLock> =
LazyLock::new(|| BankService::new(BankStorageRepository::configured()));
static HOT_BANK_SERVICE: LazyLock<
diff --git a/arma/server/extension/src/config.rs b/arma/server/extension/src/config.rs
new file mode 100644
index 0000000..7ffc835
--- /dev/null
+++ b/arma/server/extension/src/config.rs
@@ -0,0 +1,79 @@
+//! Extension configuration for SurrealDB-backed persistence.
+
+use serde::Deserialize;
+use std::fs;
+use std::path::PathBuf;
+use std::sync::OnceLock;
+
+use crate::log::log;
+
+static CONFIG_CACHE: OnceLock = OnceLock::new();
+
+#[derive(Debug, Clone, Deserialize, Default)]
+pub struct Config {
+ #[serde(default)]
+ pub surreal: SurrealConfig,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct SurrealConfig {
+ pub endpoint: String,
+ pub namespace: String,
+ pub database: String,
+ pub username: Option,
+ pub password: Option,
+ pub connect_timeout_ms: Option,
+}
+
+impl Default for SurrealConfig {
+ fn default() -> Self {
+ Self {
+ endpoint: "127.0.0.1:8000".to_string(),
+ namespace: "forge".to_string(),
+ database: "main".to_string(),
+ username: Some("root".to_string()),
+ password: Some("root".to_string()),
+ connect_timeout_ms: Some(5000),
+ }
+ }
+}
+
+pub fn load() -> Config {
+ CONFIG_CACHE
+ .get_or_init(|| {
+ let config_path = std::env::current_exe()
+ .ok()
+ .and_then(|exe| {
+ exe.parent()
+ .map(|dir| dir.join("@forge_server").join("config.toml"))
+ })
+ .filter(|path| path.exists())
+ .unwrap_or_else(|| PathBuf::from("@forge_server/config.toml"));
+
+ match fs::read_to_string(&config_path) {
+ Ok(contents) => {
+ log("main", "INFO", "Config file found. Loading.");
+ match toml::from_str::(&contents) {
+ Ok(config) => config,
+ Err(error) => {
+ log(
+ "main",
+ "ERROR",
+ &format!(
+ "Failed to parse config file '{}': {}. Using defaults.",
+ config_path.display(),
+ error
+ ),
+ );
+ Config::default()
+ }
+ }
+ }
+ Err(_) => {
+ log("main", "INFO", "Config file not found. Using defaults.");
+ Config::default()
+ }
+ }
+ })
+ .clone()
+}
diff --git a/arma/server/extension/src/icom.rs b/arma/server/extension/src/icom.rs
index 92f3807..43144a1 100644
--- a/arma/server/extension/src/icom.rs
+++ b/arma/server/extension/src/icom.rs
@@ -80,20 +80,18 @@ pub async fn initialize(ctx: Context, address: String, server_id: String) {
if let Some(client) = ICOM_CLIENT.get() {
let result = client
.listen_for_events(|msg| {
- match msg {
- Message::Event {
- event_name, data, ..
- } => {
- log::log(
- "icom",
- "INFO",
- &format!("Received event '{}': {}", event_name, data),
- );
+ if let Message::Event {
+ event_name, data, ..
+ } = msg
+ {
+ log::log(
+ "icom",
+ "INFO",
+ &format!("Received event '{}': {}", event_name, data),
+ );
- // Forward event to Arma
- forward(&event_name, &data);
- }
- _ => {}
+ // Forward event to Arma
+ forward(&event_name, &data);
}
Ok(())
})
diff --git a/arma/server/extension/src/lib.rs b/arma/server/extension/src/lib.rs
index b7b7ce6..7d4e4a1 100644
--- a/arma/server/extension/src/lib.rs
+++ b/arma/server/extension/src/lib.rs
@@ -1,20 +1,20 @@
//! Entry point and runtime bootstrap for the Forge Arma server extension.
//!
-//! Initializes a global async runtime, the Redis connection pool, and registers
+//! Initializes a global async runtime, SurrealDB persistence, and registers
//! all command groups. Provides status/version commands and maintains a shared
//! Arma `Context` for engine interop.
//!
#![allow(future_incompatible)] // Future-incompatible lint is triggered by arma_rs
use arma_rs::{Context, Extension, Group, arma};
-use std::sync::{LazyLock, OnceLock, RwLock as StdRwLock};
+use std::sync::LazyLock;
use tokio::runtime::{Builder, Runtime};
use tokio::sync::RwLock as TokioRwLock;
pub mod actor;
-pub mod adapters;
pub mod bank;
pub mod cad;
+pub mod config;
pub mod garage;
pub mod helpers;
pub mod icom;
@@ -22,7 +22,6 @@ pub mod locker;
mod log;
pub mod org;
pub mod phone;
-pub mod redis;
pub mod schema;
pub mod storage;
pub mod store;
@@ -37,10 +36,6 @@ pub mod v_locker;
/// commands that need engine interop. Stored inside an async `RwLock` to
/// allow mutation by the startup task and later reads.
static CONTEXT: LazyLock>> = LazyLock::new(|| TokioRwLock::new(None));
-/// Global Redis connection pool, created once and shared by all commands.
-/// Initialized asynchronously after `init()` returns so the extension starts
-/// quickly without blocking the main thread.
-static REDIS_POOL: OnceLock = OnceLock::new();
/// Global multi-threaded Tokio runtime used to execute async operations from
/// command handlers and startup tasks.
pub(crate) static RUNTIME: LazyLock = LazyLock::new(|| {
@@ -50,16 +45,6 @@ pub(crate) static RUNTIME: LazyLock = LazyLock::new(|| {
.expect("Failed to create tokio runtime")
});
-#[derive(Clone, Copy, PartialEq)]
-/// Connection state for the Redis pool so SQF can gate behavior on readiness.
-enum ConnectionState {
- Initializing,
- Connected,
- Failed,
-}
-static CONNECTION_STATE: LazyLock> =
- LazyLock::new(|| StdRwLock::new(ConnectionState::Initializing));
-
pub(crate) fn enqueue_persistence_task(module: &'static str, job: F)
where
F: FnOnce() -> Result<(), String> + Send + 'static,
@@ -77,14 +62,12 @@ where
#[arma]
/// Initializes the extension, registers commands/groups, and asynchronously
-/// creates the Redis connection pool on the global runtime.
+/// connects SurrealDB on the global runtime.
fn init() -> Extension {
- let config = redis::config::load();
- let storage_backend = config.storage.backend;
+ let config = config::load();
let ext = Extension::build()
.command("version", get_version)
.command("status", get_status)
- .group("redis", redis::group())
.group("surreal", surreal::group())
.group("actor", actor::group())
.group("bank", bank::group())
@@ -106,34 +89,20 @@ fn init() -> Extension {
)
.finish();
- // Spawn initialization tasks for Redis and ICOM
- // These run asynchronously and don't block extension startup
- // Redis initialization will set the global CONTEXT
- if storage_backend == redis::config::StorageBackend::Surreal {
- let surreal_config = config.surreal.clone();
- surreal::prepare();
- RUNTIME.spawn(async move {
- surreal::initialize(surreal_config).await;
- });
- }
-
+ let surreal_config = config.surreal.clone();
+ surreal::prepare();
RUNTIME.spawn(async move {
- redis::initialize(config.redis).await;
+ surreal::initialize(surreal_config).await;
});
ext
}
-/// Returns current Redis connection state as a string: `initializing`,
+/// Returns current persistence connection state as a string: `initializing`,
/// `connected`, or `failed`. Intended for SQF polling before issuing
-/// operations that require Redis.
+/// operations that require persistence.
fn get_status() -> String {
- let state = *CONNECTION_STATE.read().unwrap();
- match state {
- ConnectionState::Initializing => "initializing".into(),
- ConnectionState::Connected => "connected".into(),
- ConnectionState::Failed => "failed".into(),
- }
+ surreal::status()
}
/// Returns the extension version string for diagnostics and tooling.
diff --git a/arma/server/extension/src/log.rs b/arma/server/extension/src/log.rs
index 8ec4660..7103451 100644
--- a/arma/server/extension/src/log.rs
+++ b/arma/server/extension/src/log.rs
@@ -40,7 +40,7 @@ pub fn log(category: &str, level: &str, message: &str) {
.create(true)
.append(true)
.open(path)
- .expect(&format!("Failed to open {} log file", category))
+ .unwrap_or_else(|_| panic!("Failed to open {} log file", category))
});
let _ = file.write_all(log_entry.as_bytes());
diff --git a/arma/server/extension/src/org.rs b/arma/server/extension/src/org.rs
index a328260..89a7d11 100644
--- a/arma/server/extension/src/org.rs
+++ b/arma/server/extension/src/org.rs
@@ -21,7 +21,7 @@ use crate::storage::OrgStorageRepository;
/// Global organization service instance.
///
-/// Lazily initialized singleton combining Redis adapter, repository, and service layers.
+/// Lazily initialized singleton combining repository and service layers.
static ORG_SERVICE: LazyLock> =
LazyLock::new(|| OrgService::new(OrgStorageRepository::configured()));
static HOT_ORG_SERVICE: LazyLock<
@@ -504,7 +504,7 @@ pub fn get_members(key: String) -> String {
/// Adds a new member to an organization by their UID.
///
/// Resolves organization key to ID and adds the member UID.
-/// Redis sets automatically prevent duplicate members.
+/// Member collections automatically prevent duplicate members.
pub fn add_member(key: String, member_uid: String) -> String {
match ORG_SERVICE.add_member(key, member_uid) {
Ok(_) => "OK".to_string(),
diff --git a/arma/server/extension/src/redis/README.md b/arma/server/extension/src/redis/README.md
deleted file mode 100644
index 5fd4165..0000000
--- a/arma/server/extension/src/redis/README.md
+++ /dev/null
@@ -1,281 +0,0 @@
-# Redis Module
-
-This module provides comprehensive Redis operations for the Forge extension, enabling persistent data storage and retrieval from SQF scripts.
-
-## Architecture
-
-The Redis module is organized into specialized operation groups:
-
-- **Common**: Basic key-value operations
-- **Hash**: Structured data storage (field-value pairs)
-- **List**: Ordered collections and queues
-- **Set**: Unique collections and membership tracking
-
-## Connection Management
-
-### Connection Pool
-
-The module uses `bb8` for connection pooling, providing:
-
-- **Automatic connection reuse**: Reduces overhead
-- **Configurable pool size**: Control max/min connections
-- **Idle timeout**: Prevents stale connections
-- **Lazy initialization**: Pool created on first use
-
-### Configuration
-
-Redis connection settings are loaded from `@forge_server/config.toml`:
-
-```toml
-[redis]
-host = "127.0.0.1"
-port = 6379
-password = "" # Optional
-max_connections = 10
-min_connections = 2
-idle_timeout = 300 # seconds
-```
-
-## Common Operations
-
-Basic key-value operations for simple data storage.
-
-### Available Commands
-
-| Command | Description | Returns |
-| ------------------- | ------------------------- | ---------------------- |
-| `redis:common:set` | Set a string value | "OK" |
-| `redis:common:get` | Get a string value | Value or empty string |
-| `redis:common:incr` | Increment a numeric value | New value |
-| `redis:common:decr` | Decrement a numeric value | New value |
-| `redis:common:del` | Delete a key | Number of keys removed |
-| `redis:common:keys` | List all keys | Comma-separated keys |
-
-### SQF Examples
-
-```sqf
-// Set a value
-"forge_server" callExtension ["redis:common:set", ["player_count", "42"]];
-
-// Get a value
-private _result = "forge_server" callExtension ["redis:common:get", ["player_count"]];
-private _count = _result select 0; // "42"
-
-// Increment
-"forge_server" callExtension ["redis:common:incr", ["player_count", 1]];
-
-// Delete
-"forge_server" callExtension ["redis:common:del", ["player_count"]];
-```
-
-## Hash Operations
-
-Hash operations store structured data as field-value pairs, ideal for objects and entities.
-
-### Available Commands
-
-| Command | Description | Returns |
-| ------------------- | ------------------------------ | ------------------------ |
-| `redis:hash:set` | Set a single field | 1 if new, 0 if updated |
-| `redis:hash:mset` | Set multiple fields atomically | "OK" |
-| `redis:hash:get` | Get a field value | Value or empty string |
-| `redis:hash:getall` | Get all fields and values | Comma-separated pairs |
-| `redis:hash:del` | Delete a field | Number of fields removed |
-| `redis:hash:keys` | Get all field names | Comma-separated keys |
-| `redis:hash:vals` | Get all values | Comma-separated values |
-| `redis:hash:len` | Get number of fields | Field count |
-| `redis:hash:exists` | Check if field exists | "1" or "0" |
-
-### SQF Examples
-
-```sqf
-// Set a single field
-"forge_server" callExtension ["redis:hash:set", ["actor:76561198123456789", "name", "John Doe"]];
-
-// Set multiple fields atomically
-private _fields = [
- ["name", "John Doe"],
- ["bank", "1000"],
- ["level", "5"]
-];
-"forge_server" callExtension ["redis:hash:mset", ["actor:76561198123456789", _fields]];
-
-// Get a field
-private _result = "forge_server" callExtension ["redis:hash:get", ["actor:76561198123456789", "name"]];
-private _name = _result select 0; // "John Doe"
-
-// Get all fields
-private _result = "forge_server" callExtension ["redis:hash:getall", ["actor:76561198123456789"]];
-// Returns: "name, John Doe, bank, 1000, level, 5"
-
-// Check if field exists
-private _result = "forge_server" callExtension ["redis:hash:exists", ["actor:76561198123456789", "name"]];
-private _exists = (_result select 0) == "1";
-```
-
-## List Operations
-
-List operations manage ordered collections, useful for queues, logs, and sequential data.
-
-### Available Commands
-
-| Command | Description | Returns |
-| ------------------ | --------------------- | ------------------------------ |
-| `redis:list:set` | Set element at index | "OK" |
-| `redis:list:get` | Get element at index | Value (base64 decoded) |
-| `redis:list:len` | Get list length | Element count |
-| `redis:list:range` | Get range of elements | JSON array |
-| `redis:list:lpush` | Prepend to list | New length |
-| `redis:list:rpush` | Append to list | New length |
-| `redis:list:lpop` | Remove from beginning | JSON array of removed elements |
-| `redis:list:rpop` | Remove from end | JSON array of removed elements |
-| `redis:list:trim` | Trim to range | "OK" |
-| `redis:list:del` | Remove by value | Number removed |
-
-### SQF Examples
-
-```sqf
-// Append to list
-"forge_server" callExtension ["redis:list:rpush", ["event_log", "Player joined"]];
-"forge_server" callExtension ["redis:list:rpush", ["event_log", "Player spawned"]];
-
-// Get range
-private _result = "forge_server" callExtension ["redis:list:range", ["event_log", 0, -1]];
-private _events = parseJSON (_result select 0); // Array of all events
-
-// Pop from end
-private _result = "forge_server" callExtension ["redis:list:rpop", ["event_log", 1]];
-private _lastEvent = parseJSON (_result select 0); // ["Player spawned"]
-
-// Trim to last 100 entries
-"forge_server" callExtension ["redis:list:trim", ["event_log", -100, -1]];
-```
-
-> [!NOTE]
-> List values are automatically base64 encoded/decoded to handle special characters safely.
-
-## Set Operations
-
-Set operations manage unique collections, perfect for membership tracking and preventing duplicates.
-
-### Available Commands
-
-| Command | Description | Returns |
-| ----------------------- | ---------------------- | ---------------------------- |
-| `redis:set:add` | Add member to set | 1 if new, 0 if exists |
-| `redis:set:members` | Get all members | Comma-separated members |
-| `redis:set:card` | Get member count | Cardinality |
-| `redis:set:ismember` | Check membership | "1" or "0" |
-| `redis:set:randmember` | Get random member | Member value |
-| `redis:set:randmembers` | Get N random members | Comma-separated members |
-| `redis:set:pop` | Remove random member | Removed member |
-| `redis:set:del` | Remove specific member | 1 if removed, 0 if not found |
-
-### SQF Examples
-
-```sqf
-// Add members to a set
-"forge_server" callExtension ["redis:set:add", ["org:elite_squad:members", "76561198123456789"]];
-"forge_server" callExtension ["redis:set:add", ["org:elite_squad:members", "76561198987654321"]];
-
-// Check membership
-private _result = "forge_server" callExtension ["redis:set:ismember", ["org:elite_squad:members", "76561198123456789"]];
-private _isMember = (_result select 0) == "1";
-
-// Get all members
-private _result = "forge_server" callExtension ["redis:set:members", ["org:elite_squad:members"]];
-private _memberUIDs = (_result select 0) splitString ",";
-
-// Get member count
-private _result = "forge_server" callExtension ["redis:set:card", ["org:elite_squad:members"]];
-private _memberCount = parseNumber (_result select 0);
-
-// Remove member
-"forge_server" callExtension ["redis:set:del", ["org:elite_squad:members", "76561198123456789"]];
-```
-
-## Helper Utilities
-
-### Base64 Encoding
-
-List operations use base64 encoding to safely store complex strings:
-
-```rust
-use crate::redis::helpers::{encode_b64, decode_b64};
-
-let encoded = encode_b64("Complex [string] with {special} chars");
-let decoded = decode_b64(&encoded)?; // Original string
-```
-
-### Value Parsing
-
-The `parse_redis_value` function intelligently converts Redis strings to JSON types:
-
-```rust
-use crate::redis::helpers::parse_redis_value;
-
-parse_redis_value("42"); // Number(42)
-parse_redis_value("true"); // Bool(true)
-parse_redis_value("{\"key\":1}"); // Object
-parse_redis_value("text"); // String("text")
-```
-
-## Macro Usage
-
-The `redis_operation!` macro handles all connection and async boilerplate:
-
-```rust
-use crate::redis_operation;
-use bb8_redis::redis::AsyncCommands;
-
-pub fn my_redis_command(key: String) -> String {
- redis_operation!(conn => {
- match conn.get::<_, String>(&key).await {
- Ok(value) => value,
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-```
-
-The macro automatically:
-
-- Acquires a connection from the pool
-- Handles lazy initialization if needed
-- Executes the operation asynchronously
-- Returns the result to SQF
-
-## Error Handling
-
-All Redis operations return strings:
-
-- **Success**: Operation result (e.g., "OK", value, count)
-- **Error**: String starting with "Error: " followed by the error message
-
-```sqf
-private _result = "forge_server" callExtension ["redis:common:get", ["mykey"]];
-private _value = _result select 0;
-
-if (_value find "Error:" == 0) then {
- diag_log format ["Redis error: %1", _value];
-} else {
- // Use the value
- systemChat format ["Value: %1", _value];
-};
-```
-
-## Performance Considerations
-
-- **Connection Pooling**: Reuses connections to minimize overhead
-- **Async Operations**: Non-blocking I/O prevents server lag
-- **Atomic Operations**: `hash:mset` sets multiple fields in one operation
-- **Batch Operations**: Use lists and sets for bulk data
-
-## Best Practices
-
-1. **Use Hashes for Objects**: Store actor/org data as hash fields
-2. **Use Sets for Membership**: Track organization members, online players
-3. **Use Lists for Logs**: Event logs, chat history, audit trails
-4. **Prefix Keys**: Use namespaces like `actor:`, `org:`, `vehicle:`
-5. **Handle Errors**: Always check for "Error:" prefix in results
-6. **Atomic Updates**: Use `hash:mset` instead of multiple `hash:set` calls
diff --git a/arma/server/extension/src/redis/client.rs b/arma/server/extension/src/redis/client.rs
deleted file mode 100644
index 0ebc910..0000000
--- a/arma/server/extension/src/redis/client.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use super::config::RedisConfig;
-use bb8_redis::{RedisConnectionManager, bb8};
-use std::error::Error;
-use std::time::Duration;
-
-/// Redis connection pool type alias.
-pub type RedisClient = bb8::Pool;
-
-/// Creates a Redis connection pool with the specified configuration.
-pub async fn create_redis_pool(
- config: &RedisConfig,
-) -> Result> {
- // Generate the Redis connection string from configuration
- let connection_string = config.connection_string();
-
- // Create the connection manager that will handle individual connections
- let manager = RedisConnectionManager::new(connection_string)?;
-
- // Start building the connection pool with default settings
- let mut pool_builder = bb8::Pool::builder();
-
- // Configure maximum number of connections if specified
- // This prevents overwhelming the Redis server with too many connections
- if let Some(max_conn) = config.max_connections {
- pool_builder = pool_builder.max_size(max_conn as u32);
- }
-
- // Configure minimum idle connections if specified
- // This ensures quick response times by keeping connections ready
- if let Some(min_conn) = config.min_connections {
- pool_builder = pool_builder.min_idle(Some(min_conn as u32));
- }
-
- // Configure idle connection timeout if specified
- // This prevents keeping stale connections that might be closed by the server
- if let Some(idle_timeout) = config.idle_timeout {
- pool_builder = pool_builder.idle_timeout(Some(Duration::from_secs(idle_timeout)));
- }
-
- // Bound connection acquisition from the pool so game thread calls fail fast
- if let Some(connect_timeout_ms) = config.connect_timeout_ms {
- pool_builder = pool_builder.connection_timeout(Duration::from_millis(connect_timeout_ms));
- }
-
- // Build the final connection pool with all configured parameters
- let pool = pool_builder.build(manager).await?;
- Ok(pool)
-}
diff --git a/arma/server/extension/src/redis/common.rs b/arma/server/extension/src/redis/common.rs
deleted file mode 100644
index 4ddce19..0000000
--- a/arma/server/extension/src/redis/common.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-//! Common Redis operations for basic key-value functionality.
-
-use crate::redis_operation;
-use bb8_redis::redis::AsyncCommands;
-
-/// Sets a string value for the specified Redis key.
-pub fn set_key(key: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.set(&key, &value).await {
- Ok(()) => "OK".to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves the string value for the specified Redis key.
-pub fn get_key(key: String) -> String {
- redis_operation!(conn => {
- match conn.get::<_, String>(&key).await {
- Ok(value) => value,
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Increments a numeric value stored at the specified key.
-pub fn incr_key(key: String, count: usize) -> String {
- redis_operation!(conn => {
- match conn.incr::<_, _, i64>(&key, count).await {
- Ok(value) => value.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Decrements a numeric value stored at the specified key.
-pub fn decr_key(key: String, count: usize) -> String {
- redis_operation!(conn => {
- match conn.decr::<_, _, i64>(&key, count).await {
- Ok(value) => value.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Checks if a Redis key exists.
-pub fn key_exists(key: String) -> String {
- redis_operation!(conn => {
- match conn.exists::<_, i32>(&key).await {
- Ok(exists) => exists.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Deletes a Redis key and its associated value.
-pub fn delete_key(key: String) -> String {
- redis_operation!(conn => {
- match conn.del::<_, usize>(&key).await {
- Ok(removed) => removed.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Lists all Redis keys matching the wildcard pattern "*".
-pub fn list_keys() -> String {
- redis_operation!(conn => {
- match conn.keys::<_, Vec>("*").await {
- Ok(keys) => keys.join(","),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
diff --git a/arma/server/extension/src/redis/config.rs b/arma/server/extension/src/redis/config.rs
deleted file mode 100644
index 0da1ea4..0000000
--- a/arma/server/extension/src/redis/config.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-//! Configuration management for Redis connection and application settings.
-
-use serde::Deserialize;
-use std::fs;
-use std::path::PathBuf;
-use std::sync::OnceLock;
-
-use crate::log::log;
-
-static CONFIG_CACHE: OnceLock = OnceLock::new();
-
-/// Main configuration structure for the entire application.
-#[derive(Debug, Clone, Deserialize)]
-pub struct Config {
- /// Durable storage backend selector.
- #[serde(default)]
- pub storage: StorageConfig,
- /// Redis configuration with automatic defaults if not specified
- #[serde(default)]
- pub redis: RedisConfig,
- /// SurrealDB configuration with automatic defaults if not specified
- #[serde(default)]
- pub surreal: SurrealConfig,
-}
-
-impl Default for Config {
- /// Creates a default configuration with sensible values for development.
- fn default() -> Self {
- Self {
- storage: StorageConfig::default(),
- redis: RedisConfig::default(),
- surreal: SurrealConfig::default(),
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "lowercase")]
-pub enum StorageBackend {
- Redis,
- Surreal,
-}
-
-impl Default for StorageBackend {
- fn default() -> Self {
- Self::Redis
- }
-}
-
-/// Durable storage backend selection.
-#[derive(Debug, Clone, Deserialize)]
-pub struct StorageConfig {
- #[serde(default)]
- pub backend: StorageBackend,
-}
-
-impl Default for StorageConfig {
- fn default() -> Self {
- Self {
- backend: StorageBackend::Redis,
- }
- }
-}
-
-/// Redis connection and connection pool configuration.
-#[derive(Debug, Clone, Deserialize)]
-pub struct RedisConfig {
- /// Redis server hostname or IP address
- pub host: String,
- /// Redis server port number
- pub port: u16,
- /// Redis database number (0-15)
- pub db: u8,
- /// Username for Redis ACL authentication (Redis 6.0+)
- pub username: Option,
- /// Password for Redis authentication
- pub password: Option,
- /// Maximum number of connections in the pool
- pub max_connections: Option,
- /// Minimum number of idle connections to maintain
- pub min_connections: Option,
- /// Idle connection timeout in seconds
- pub idle_timeout: Option,
- /// Maximum time to wait for pool connection checkout in milliseconds
- pub pool_get_timeout_ms: Option,
- /// Maximum time to wait for individual Redis command execution in milliseconds
- pub command_timeout_ms: Option,
- /// Maximum time to wait for pool connection establishment in milliseconds
- pub connect_timeout_ms: Option,
-}
-
-impl Default for RedisConfig {
- /// Creates default Redis configuration suitable for local development.
- fn default() -> Self {
- Self {
- host: "127.0.0.1".to_string(),
- port: 6379,
- db: 0,
- username: None,
- password: None,
- max_connections: Some(10),
- min_connections: Some(2),
- idle_timeout: Some(60),
- pool_get_timeout_ms: Some(2000),
- command_timeout_ms: Some(2000),
- connect_timeout_ms: Some(2000),
- }
- }
-}
-
-/// SurrealDB connection configuration.
-#[derive(Debug, Clone, Deserialize)]
-pub struct SurrealConfig {
- /// SurrealDB HTTP endpoint, for example `127.0.0.1:8000`.
- pub endpoint: String,
- /// SurrealDB namespace.
- pub namespace: String,
- /// SurrealDB database.
- pub database: String,
- /// Optional root username for authentication.
- pub username: Option,
- /// Optional root password for authentication.
- pub password: Option,
- /// Maximum time to wait for initial connection in milliseconds.
- pub connect_timeout_ms: Option,
-}
-
-impl Default for SurrealConfig {
- fn default() -> Self {
- Self {
- endpoint: "127.0.0.1:8000".to_string(),
- namespace: "forge".to_string(),
- database: "main".to_string(),
- username: Some("root".to_string()),
- password: Some("root".to_string()),
- connect_timeout_ms: Some(5000),
- }
- }
-}
-
-impl RedisConfig {
- /// Generates a Redis connection string from the configuration.
- pub fn connection_string(&self) -> String {
- // Build authentication part of the URL
- let auth_part = match (&self.username, &self.password) {
- (Some(username), Some(password)) => format!("{}:{}@", username, password),
- (None, Some(password)) => format!(":{}@", password),
- (Some(username), None) => format!("{}@", username),
- (None, None) => String::new(),
- };
-
- let mut conn_str = format!("redis://{}{}", auth_part, self.host);
-
- if self.port != 6379 {
- conn_str.push_str(&format!(":{}", self.port));
- }
-
- if self.db != 0 {
- conn_str.push_str(&format!("/{}", self.db));
- }
-
- log(
- "main",
- "INFO",
- &format!("Redis connection string: {}", conn_str),
- );
-
- conn_str
- }
-}
-
-/// Loads configuration from the `config.toml` file with graceful fallback to defaults.
-pub fn load() -> Config {
- CONFIG_CACHE
- .get_or_init(|| {
- let config_path = std::env::current_exe()
- .ok()
- .and_then(|exe| {
- exe.parent()
- .map(|dir| dir.join("@forge_server").join("config.toml"))
- })
- .filter(|p| p.exists())
- .unwrap_or_else(|| PathBuf::from("@forge_server/config.toml"));
-
- match fs::read_to_string(&config_path) {
- Ok(contents) => {
- log("main", "INFO", &format!("Config file found! Loading..."));
- match toml::from_str::(&contents) {
- Ok(config) => config,
- Err(error) => {
- log(
- "main",
- "ERROR",
- &format!(
- "Failed to parse config file '{}': {}. Using defaults.",
- config_path.display(),
- error
- ),
- );
- Config::default()
- }
- }
- }
- Err(_) => {
- log(
- "main",
- "INFO",
- &format!("Config file not found. Using default configuration."),
- );
- Config::default()
- }
- }
- })
- .clone()
-}
diff --git a/arma/server/extension/src/redis/hash.rs b/arma/server/extension/src/redis/hash.rs
deleted file mode 100644
index 399ba45..0000000
--- a/arma/server/extension/src/redis/hash.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-//! Redis hash operations for structured data storage.
-
-use crate::redis_operation;
-use bb8_redis::redis::AsyncCommands;
-use std::collections::HashMap;
-
-/// Sets a single field in a Redis hash.
-pub fn hash_set(key: String, field: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.hset::<_, _, _, i32>(&key, &field, &value).await {
- Ok(added) => added.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Sets multiple fields in a Redis hash atomically.
-pub fn hash_mset(key: String, items: Vec<(String, String)>) -> String {
- redis_operation!(conn => {
- match conn.hset_multiple(&key, &items).await {
- Ok(()) => "OK".to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves the value of a specific field from a Redis hash.
-pub fn hash_get(key: String, field: String) -> String {
- redis_operation!(conn => {
- match conn.hget::<_, _, Option>(&key, &field).await {
- Ok(Some(value)) => value,
- Ok(None) => String::new(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves all fields and values from a Redis hash.
-pub fn hash_get_all(key: String) -> String {
- redis_operation!(conn => {
- match conn.hgetall::<_, HashMap>(&key).await {
- Ok(hash_map) => match serde_json::to_string(&hash_map) {
- Ok(json) => json,
- Err(e) => format!("Error: Failed to serialize hash map: {}", e),
- },
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes a field from a Redis hash.
-pub fn hash_del(key: String, field: String) -> String {
- redis_operation!(conn => {
- match conn.hdel::<_, _, i32>(&key, &field).await {
- Ok(removed) => removed.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves all field names from a Redis hash.
-pub fn hash_keys(key: String) -> String {
- redis_operation!(conn => {
- match conn.hkeys::<_, Vec>(&key).await {
- Ok(fields) => fields.join(","),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves all values from a Redis hash.
-pub fn hash_values(key: String) -> String {
- redis_operation!(conn => {
- match conn.hvals::<_, Vec>(&key).await {
- Ok(values) => values.join(","),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Returns the number of fields in a Redis hash.
-pub fn hash_len(key: String) -> String {
- redis_operation!(conn => {
- match conn.hlen::<_, i32>(&key).await {
- Ok(len) => len.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Tests if a field exists in a Redis hash.
-pub fn hash_exists(key: String, field: String) -> String {
- redis_operation!(conn => {
- match conn.hexists::<_, _, bool>(&key, &field).await {
- Ok(exists) => if exists { "1" } else { "0" }.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
diff --git a/arma/server/extension/src/redis/helpers.rs b/arma/server/extension/src/redis/helpers.rs
deleted file mode 100644
index 04bfcd5..0000000
--- a/arma/server/extension/src/redis/helpers.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-//! Helper utilities for Redis data processing and encoding.
-
-use serde_json;
-
-/// Intelligently parses a Redis string value into the appropriate JSON type.
-#[allow(dead_code)]
-pub fn parse_redis_value(value: &str) -> serde_json::Value {
- // Handle empty strings as null values
- if value.is_empty() {
- return serde_json::Value::Null;
- }
-
- // Try to parse as JSON first (handles objects, arrays, and JSON primitives)
- if let Ok(json_val) = serde_json::from_str(value) {
- // Special handling: unwrap single-element arrays
- if let serde_json::Value::Array(arr) = &json_val {
- if arr.len() == 1 {
- return arr[0].clone();
- }
- }
- return json_val;
- }
-
- // Try to parse as integer
- if let Ok(int_val) = value.parse::() {
- return serde_json::Value::Number(serde_json::Number::from(int_val));
- }
-
- // Try to parse as float
- if let Ok(float_val) = value.parse::() {
- if let Some(num) = serde_json::Number::from_f64(float_val) {
- return serde_json::Value::Number(num);
- }
- }
-
- // Try to parse as boolean (case-insensitive)
- match value.to_lowercase().as_str() {
- "true" => return serde_json::Value::Bool(true),
- "false" => return serde_json::Value::Bool(false),
- _ => {}
- }
-
- // Fallback: treat as string
- serde_json::Value::String(value.to_string())
-}
-
-/// Converts a JSON value to a string by wrapping it in an array.
-#[allow(dead_code)]
-pub fn parse_json_value(value: &serde_json::Value) -> String {
- // Wrap the value in a single-element array
- let wrapped = serde_json::Value::Array(vec![value.clone()]);
-
- // Serialize the wrapped array to a JSON string
- wrapped.to_string()
-}
-
-/// Encodes a string to base64 for safe Redis storage.
-pub fn encode_b64(data: &str) -> String {
- use base64::{Engine as _, engine::general_purpose};
- general_purpose::STANDARD.encode(data.as_bytes())
-}
-
-/// Decodes a base64 string back to its original form.
-pub fn decode_b64(encoded: &str) -> Result {
- use base64::{Engine as _, engine::general_purpose};
- match general_purpose::STANDARD.decode(encoded) {
- Ok(bytes) => match String::from_utf8(bytes) {
- Ok(string) => Ok(string),
- Err(e) => Err(format!("Invalid UTF-8: {}", e)),
- },
- Err(e) => Err(format!("Invalid base64: {}", e)),
- }
-}
diff --git a/arma/server/extension/src/redis/list.rs b/arma/server/extension/src/redis/list.rs
deleted file mode 100644
index 07068d8..0000000
--- a/arma/server/extension/src/redis/list.rs
+++ /dev/null
@@ -1,167 +0,0 @@
-//! Redis list operations for ordered collections and queues.
-
-use crate::redis_operation;
-use bb8_redis::redis::AsyncCommands;
-
-/// Sets the value of an element at a specific index in a Redis list.
-pub fn list_set(key: String, index: isize, value: String) -> String {
- use crate::redis::helpers::encode_b64;
- let encoded_value = encode_b64(&value);
- redis_operation!(conn => {
- match conn.lset(&key, index, &encoded_value).await {
- Ok(()) => "OK".to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves the value of an element at a specific index in a Redis list.
-pub fn list_get(key: String, index: isize) -> String {
- use crate::redis::helpers::decode_b64;
- redis_operation!(conn => {
- match conn.lindex::<_, String>(&key, index).await {
- Ok(encoded_value) => {
- match decode_b64(&encoded_value) {
- Ok(decoded) => decoded,
- Err(e) => format!("Error decoding base64: {}", e),
- }
- },
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Returns the length (number of elements) of a Redis list.
-pub fn list_len(key: String) -> String {
- redis_operation!(conn => {
- match conn.llen::<_, i32>(&key).await {
- Ok(len) => len.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves a range of elements from a Redis list.
-pub fn list_range(key: String, start: isize, end: isize) -> String {
- use crate::redis::helpers::decode_b64;
- redis_operation!(conn => {
- match conn.lrange::<_, Vec>(&key, start, end).await {
- Ok(encoded_values) => {
- let mut decoded_values = Vec::new();
- for encoded in encoded_values {
- match decode_b64(&encoded) {
- Ok(decoded) => decoded_values.push(decoded),
- Err(e) => return format!("Error decoding base64: {}", e),
- }
- }
- match serde_json::to_string(&decoded_values) {
- Ok(json_array) => json_array,
- Err(e) => format!("Error: Failed to serialize to JSON: {}", e),
- }
- },
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Prepends a value to the beginning (left) of a Redis list.
-pub fn list_lpush(key: String, value: String) -> String {
- use crate::redis::helpers::encode_b64;
- let encoded_value = encode_b64(&value);
- redis_operation!(conn => {
- match conn.lpush::<_, _, usize>(&key, &encoded_value).await {
- Ok(len) => len.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Appends a value to the end (right) of a Redis list.
-pub fn list_rpush(key: String, value: String) -> String {
- use crate::redis::helpers::encode_b64;
- let encoded_value = encode_b64(&value);
- redis_operation!(conn => {
- match conn.rpush::<_, _, usize>(&key, &encoded_value).await {
- Ok(len) => len.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes and returns elements from the beginning (left) of a Redis list.
-pub fn list_lpop(key: String, count: usize) -> String {
- use crate::redis::helpers::decode_b64;
- redis_operation!(conn => {
- let count_option = if count == 0 {
- None
- } else {
- std::num::NonZeroUsize::new(count)
- };
- match conn.lpop::<_, Vec>(&key, count_option).await {
- Ok(encoded_values) => {
- let mut decoded_values = Vec::new();
- for encoded in encoded_values {
- match decode_b64(&encoded) {
- Ok(decoded) => decoded_values.push(decoded),
- Err(e) => return format!("Error decoding base64: {}", e),
- }
- }
- match serde_json::to_string(&decoded_values) {
- Ok(json_array) => json_array,
- Err(e) => format!("Error: Failed to serialize to JSON: {}", e),
- }
- },
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes and returns elements from the end (right) of a Redis list.
-pub fn list_rpop(key: String, count: usize) -> String {
- use crate::redis::helpers::decode_b64;
- redis_operation!(conn => {
- let count_option = if count == 0 {
- None
- } else {
- std::num::NonZeroUsize::new(count)
- };
- match conn.rpop::<_, Vec>(&key, count_option).await {
- Ok(encoded_values) => {
- let mut decoded_values = Vec::new();
- for encoded in encoded_values {
- match decode_b64(&encoded) {
- Ok(decoded) => decoded_values.push(decoded),
- Err(e) => return format!("Error decoding base64: {}", e),
- }
- }
- match serde_json::to_string(&decoded_values) {
- Ok(json_array) => json_array,
- Err(e) => format!("Error: Failed to serialize to JSON: {}", e),
- }
- },
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Trims a Redis list to keep only elements within the specified range.
-pub fn list_trim(key: String, start: isize, end: isize) -> String {
- redis_operation!(conn => {
- match conn.ltrim(&key, start, end).await {
- Ok(()) => "OK".to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes elements from a Redis list by value.
-pub fn list_del(key: String, count: isize, value: String) -> String {
- use crate::redis::helpers::encode_b64;
- let encoded_value = encode_b64(&value);
- redis_operation!(conn => {
- match conn.lrem::<_, _, i32>(&key, count, &encoded_value).await {
- Ok(removed) => removed.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
diff --git a/arma/server/extension/src/redis/macros.rs b/arma/server/extension/src/redis/macros.rs
deleted file mode 100644
index 80e40ff..0000000
--- a/arma/server/extension/src/redis/macros.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-//! Macros for Redis operation boilerplate reduction.
-
-/// Macro for Redis operations that handles all connection and async boilerplate.
-#[macro_export]
-macro_rules! redis_operation {
- ($conn:ident => $operation:block) => {{
- use tokio::time::{Duration, timeout};
- use $crate::redis;
- use $crate::{CONNECTION_STATE, ConnectionState, REDIS_POOL, RUNTIME};
-
- let timeout_config = redis::config::load().redis;
- let pool_get_timeout =
- Duration::from_millis(timeout_config.pool_get_timeout_ms.unwrap_or(2000));
- let command_timeout =
- Duration::from_millis(timeout_config.command_timeout_ms.unwrap_or(2000));
- let init_timeout = Duration::from_millis(timeout_config.connect_timeout_ms.unwrap_or(2000));
-
- // Get the Redis connection pool (initialized at startup)
- let pool = match REDIS_POOL.get() {
- Some(pool) => pool,
- None => {
- if *CONNECTION_STATE.read().unwrap() == ConnectionState::Failed {
- return "Error: Redis connection unavailable".to_string();
- }
-
- // Attempt lazy initialization if not already initialized
- let rt = &RUNTIME;
- let init_result = rt.block_on(async move {
- let cfg = redis::config::load();
- match timeout(init_timeout, redis::client::create_redis_pool(&cfg.redis)).await
- {
- Ok(Ok(pool)) => {
- let _ = REDIS_POOL.set(pool);
- Ok(())
- }
- Ok(Err(_e)) => {
- let default_cfg = redis::RedisConfig::default();
- match timeout(
- init_timeout,
- redis::client::create_redis_pool(&default_cfg),
- )
- .await
- {
- Ok(Ok(pool)) => {
- let _ = REDIS_POOL.set(pool);
- Ok(())
- }
- Ok(Err(e)) => Err(format!("{}", e)),
- Err(_) => {
- Err("Redis fallback initialization timed out".to_string())
- }
- }
- }
- Err(_) => Err("Redis initialization timed out".to_string()),
- }
- });
-
- match init_result {
- Ok(()) => {
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Connected;
- match REDIS_POOL.get() {
- Some(pool) => pool,
- None => return "Error: Redis pool not initialized".to_string(),
- }
- }
- Err(err) => {
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Failed;
- return format!("Error: {}", err);
- }
- }
- }
- };
-
- // Use the global tokio runtime to execute async operations
- let rt = &RUNTIME;
- rt.block_on(async move {
- // Acquire a connection from the pool
- let mut $conn = match timeout(pool_get_timeout, pool.get()).await {
- Ok(Ok(conn)) => conn,
- Ok(Err(e)) => return format!("Error: {}", e),
- Err(_) => return "Error: Redis connection checkout timed out".to_string(),
- };
-
- // Execute the user-provided Redis operation
- match timeout(command_timeout, async move { $operation }).await {
- Ok(result) => result,
- Err(_) => "Error: Redis operation timed out".to_string(),
- }
- })
- }};
-}
diff --git a/arma/server/extension/src/redis/mod.rs b/arma/server/extension/src/redis/mod.rs
deleted file mode 100644
index a954ad7..0000000
--- a/arma/server/extension/src/redis/mod.rs
+++ /dev/null
@@ -1,138 +0,0 @@
-//! Redis operations and utilities for the Arma 3 server extension.
-
-use arma_rs::Group;
-use tokio::time::{Duration, timeout};
-
-pub use client::create_redis_pool;
-pub use config::RedisConfig;
-pub use helpers::{decode_b64, encode_b64};
-
-use crate::{CONNECTION_STATE, ConnectionState, REDIS_POOL, log};
-
-pub mod client;
-pub mod common;
-pub mod config;
-pub mod hash;
-pub mod helpers;
-pub mod list;
-pub mod macros;
-pub mod set;
-
-/// Initialize Redis connection pool with fallback to default config
-///
-/// This function attempts to connect to Redis using the provided config,
-/// with a 5-second timeout. If the primary config fails, it tries the
-/// default config as a fallback.
-pub async fn initialize(config: RedisConfig) {
- // Use timeout to prevent hanging if Redis is unavailable
- let pool_result = timeout(Duration::from_secs(5), create_redis_pool(&config)).await;
-
- let pool = match pool_result {
- Err(_) => {
- log::log(
- "redis",
- "ERROR",
- "Redis connection timed out after 5 seconds",
- );
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Failed;
- return; // Exit early
- }
- Ok(Ok(pool)) => {
- log::log("redis", "INFO", "Connected to Redis server");
- pool
- }
- Ok(Err(e)) => {
- log::log(
- "redis",
- "WARN",
- &format!("Failed to connect to Redis (primary config): {}", e),
- );
- // Try default config as fallback with timeout
- let default_config = RedisConfig::default();
- match timeout(Duration::from_secs(5), create_redis_pool(&default_config)).await {
- Err(_) => {
- log::log(
- "redis",
- "ERROR",
- "Redis (default config) timed out after 5 seconds",
- );
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Failed;
- return;
- }
- Ok(Ok(pool)) => {
- log::log("redis", "INFO", "Connected to Redis using default config");
- pool
- }
- Ok(Err(e)) => {
- log::log(
- "redis",
- "ERROR",
- &format!("Failed to connect to Redis (all attempts): {}", e),
- );
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Failed;
- return; // Exit early, don't set pool
- }
- }
- }
- };
-
- if REDIS_POOL.set(pool).is_ok() {
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Connected;
- } else {
- log::log("redis", "ERROR", "Failed to set Redis pool (already set)");
- *CONNECTION_STATE.write().unwrap() = ConnectionState::Failed;
- }
-}
-
-pub fn group() -> Group {
- Group::new()
- .group(
- "common",
- Group::new()
- .command("set", common::set_key)
- .command("get", common::get_key)
- .command("incr", common::incr_key)
- .command("decr", common::decr_key)
- .command("del", common::delete_key)
- .command("keys", common::list_keys),
- )
- .group(
- "hash",
- Group::new()
- .command("set", hash::hash_set)
- .command("mset", hash::hash_mset)
- .command("get", hash::hash_get)
- .command("getall", hash::hash_get_all)
- .command("del", hash::hash_del)
- .command("keys", hash::hash_keys)
- .command("vals", hash::hash_values)
- .command("len", hash::hash_len)
- .command("exists", hash::hash_exists),
- )
- .group(
- "list",
- Group::new()
- .command("set", list::list_set)
- .command("get", list::list_get)
- .command("len", list::list_len)
- .command("range", list::list_range)
- .command("lpush", list::list_lpush)
- .command("rpush", list::list_rpush)
- .command("lpop", list::list_lpop)
- .command("rpop", list::list_rpop)
- .command("trim", list::list_trim)
- .command("del", list::list_del),
- )
- .group(
- "set",
- Group::new()
- .command("add", set::set_add)
- .command("members", set::set_members)
- .command("card", set::set_card)
- .command("ismember", set::set_is_member)
- .command("randmember", set::set_random_member)
- .command("randmembers", set::set_random_members)
- .command("pop", set::set_pop)
- .command("del", set::set_del),
- )
-}
diff --git a/arma/server/extension/src/redis/set.rs b/arma/server/extension/src/redis/set.rs
deleted file mode 100644
index 1be6ce5..0000000
--- a/arma/server/extension/src/redis/set.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-//! Redis set operations for unique collections and membership tracking.
-
-use crate::redis_operation;
-use bb8_redis::redis::AsyncCommands;
-
-/// Adds a value to a Redis set.
-pub fn set_add(key: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.sadd::<_, _, i32>(&key, &value).await {
- Ok(added) => added.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Retrieves all members of a Redis set.
-pub fn set_members(key: String) -> String {
- redis_operation!(conn => {
- match conn.smembers::<_, Vec>(&key).await {
- Ok(members) => members.join(","),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Returns the number of members in a Redis set (cardinality).
-pub fn set_card(key: String) -> String {
- redis_operation!(conn => {
- match conn.scard::<_, i32>(&key).await {
- Ok(card) => card.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes a value from a Redis set.
-pub fn set_del(key: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.srem::<_, _, i32>(&key, &value).await {
- Ok(removed) => removed.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Tests if a value is a member of a Redis set.
-pub fn set_is_member(key: String, value: String) -> String {
- redis_operation!(conn => {
- match conn.sismember::<_, _, bool>(&key, &value).await {
- Ok(is_member) => if is_member { "1" } else { "0" }.to_string(),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Removes and returns a random member from a Redis set.
-pub fn set_pop(key: String) -> String {
- redis_operation!(conn => {
- match conn.spop::<_, String>(&key).await {
- Ok(value) => value,
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Returns a random member from a Redis set without removing it.
-pub fn set_random_member(key: String) -> String {
- redis_operation!(conn => {
- match conn.srandmember::<_, String>(&key).await {
- Ok(value) => value,
- Err(e) => format!("Error: {}", e),
- }
- })
-}
-
-/// Returns multiple random members from a Redis set without removing them.
-pub fn set_random_members(key: String, count: isize) -> String {
- redis_operation!(conn => {
- match conn
- .srandmember_multiple::<_, Vec>(&key, count.try_into().unwrap_or(0))
- .await
- {
- Ok(values) => values.join(","),
- Err(e) => format!("Error: {}", e),
- }
- })
-}
diff --git a/arma/server/extension/src/storage.rs b/arma/server/extension/src/storage.rs
index ca98bd2..a41aa44 100644
--- a/arma/server/extension/src/storage.rs
+++ b/arma/server/extension/src/storage.rs
@@ -27,15 +27,11 @@ use forge_models::{
};
use forge_repositories::{
ActorRepository, BankRepository, GarageRepository, LockerRepository, OrgRepository,
- PhoneRepository, RedisActorRepository, RedisBankRepository, RedisGarageRepository,
- RedisLockerRepository, RedisOrgRepository, RedisPhoneRepository, RedisVGarageRepository,
- RedisVLockerRepository, VGarageRepository, VLockerRepository,
+ PhoneRepository, VGarageRepository, VLockerRepository,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use crate::RUNTIME;
-use crate::adapters::ExtensionRedisClient;
-use crate::redis::config::{StorageBackend, load};
use crate::surreal;
diff --git a/arma/server/extension/src/storage/actor.rs b/arma/server/extension/src/storage/actor.rs
index 8b42e59..1174efb 100644
--- a/arma/server/extension/src/storage/actor.rs
+++ b/arma/server/extension/src/storage/actor.rs
@@ -2,53 +2,42 @@ use super::common::*;
use super::*;
pub enum ActorStorageRepository {
- Redis(RedisActorRepository),
Surreal(SurrealActorRepository),
}
impl ActorStorageRepository {
pub fn configured() -> Self {
- match load().storage.backend {
- StorageBackend::Surreal => Self::Surreal(SurrealActorRepository),
- StorageBackend::Redis => {
- Self::Redis(RedisActorRepository::new(ExtensionRedisClient::new()))
- }
- }
+ Self::Surreal(SurrealActorRepository)
}
}
impl ActorRepository for ActorStorageRepository {
fn create(&self, actor: &Actor) -> Result<(), String> {
match self {
- Self::Redis(repository) => repository.create(actor),
Self::Surreal(repository) => repository.create(actor),
}
}
fn get_by_id(&self, id: &str) -> Result