# Forge Repositories

This crate provides the data access layer for the Forge application, implementing the repository pattern to abstract database operations from business logic.

## Architecture

The repository layer sits between the service layer and the database, providing a clean abstraction for data persistence.

```mermaid
graph TD
    Services[Services Layer]
    Repositories[Repositories Layer<br/>#40;This Module#41;]
    Database[Database]

    Services --> Repositories
    Repositories --> Database
```

### Dual Storage Strategy
The implementation uses a dual storage strategy in Redis to optimize for different access patterns:
- **Hash Maps (`HMSET`/`HGETALL`):** Used for entity data (Actors, Organizations) to allow O(1) access to specific fields and efficient partial updates.
- **Sets (`SADD`/`SMEMBERS`):** Used for relationships (e.g., Organization Members) to ensure uniqueness and provide efficient membership testing.

## Key Features

- **Redis Integration:** Efficient hash-based storage for data
- **JSON Serialization:** Automatic conversion between Rust structs and Redis
- **Type Safety:** Strong typing with error handling
- **Performance Optimized:** Hash operations for fast field-level access
- **Flexible Client:** Generic over Redis client implementations
- **Atomic Operations:** Uses Redis atomicity guarantees for data integrity

## Actor Repository

The `ActorRepository` handles persistence for player data.

### Storage Format
Actors are stored in Redis as hash maps:
```text
actor:{uid} -> Hash {
  "uid": "76561198123456789",
  "name": "PlayerName",
  "bank": "1500.0",
  ...
}
```

### Usage Example

```rust
use forge_repositories::ActorRepository;
use forge_models::Actor;

async fn example_usage<R: ActorRepository>(repo: &R) -> Result<(), String> {
    // 1. Create
    let actor = Actor::new("76561198123456789".to_string())?;
    repo.create(&actor)?;

    // 2. Retrieve
    if let Some(retrieved) = repo.get_by_id("76561198123456789")? {
        println!("Found actor: {}", retrieved.name());
    }

    // 3. Update
    // Updates are atomic and preserve fields not present in the update
    let mut actor_to_update = repo.get_by_id("76561198123456789")?.unwrap();
    // ... modify actor ...
    repo.update(&actor_to_update)?;

    // 4. Check Existence
    if repo.exists("76561198123456789")? {
        println!("Actor exists");
    }

    // 5. Delete
    repo.delete("76561198123456789")?;

    Ok(())
}
```

## Organization Repository

The `OrgRepository` handles persistence for organizations (guilds/clans) and their members.

### Storage Format
- **Org Data:** `org:{org_id}` (Hash)
- **Members:** `org:{org_id}:members` (Set)

### Usage Example

```rust
use forge_repositories::OrgRepository;
use forge_models::{Org, MemberSummary};

async fn example_usage<R: OrgRepository>(repo: &R) -> Result<(), String> {
    // 1. Create Organization
    let org = Org::new("elite_squad".to_string(), "leader_uid".to_string(), "Elite Squad".to_string())?;
    repo.create(&org)?;

    // 2. Manage Members
    // Add a member (idempotent, handles duplicates)
    repo.add_member("elite_squad", "member_uid_1")?;

    // Get all members
    let members = repo.get_members("elite_squad")?;
    for member in members {
        println!("Member: {} ({})", member.name, member.uid);
    }

    // Remove a member
    repo.remove_member("elite_squad", "member_uid_1")?;

    // 3. Update Organization
    let mut org_update = repo.get_by_id("elite_squad")?.unwrap();
    // ... modify org ...
    repo.update(&org_update)?;

    // 4. Delete Organization
    // Note: This removes the org data but may require separate cleanup for members depending on implementation
    repo.delete("elite_squad")?;

    Ok(())
}
```

## Performance & Implementation Details

### Atomicity
- **Upserts:** `create` and `update` operations use `HMSET` which is atomic. This means either all fields are updated or none are.
- **Schema Evolution:** New fields added to the Rust structs are automatically persisted to Redis. Old fields in Redis that are no longer in the struct are **preserved** (not deleted) during updates, allowing for safe backward compatibility.

### Thread Safety
All repository implementations are `Send + Sync`, allowing them to be safely shared across threads. The underlying `RedisClient` handles connection pooling and concurrent access.

### Error Handling
Repositories return `Result<T, String>` (or custom error types) to propagate database failures, serialization errors, or validation issues up to the service layer.

## Contributing

We welcome contributions to the Forge Repository Layer! This guide will help you understand how to add new repositories and maintain the existing codebase.

### Adding a New Repository

To add a new repository (e.g., `ItemRepository`), follow these steps:

1.  **Create the Module**: Create a new file in `src/` (e.g., `src/item.rs`).
2.  **Define the Trait**: Define a trait that specifies the data access operations. Ensure it requires `Send + Sync`.
    ```rust
    pub trait ItemRepository: Send + Sync {
        fn create(&self, item: &Item) -> Result<(), String>;
        fn get_by_id(&self, id: &str) -> Result<Option<Item>, String>;
        // ... other methods
    }
    ```
3.  **Implement for Redis**: Implement the trait for a generic `RedisClient`. Use `forge_shared` helpers for value conversion if needed.
    ```rust
    use forge_shared::{RedisClient, parse_json_value, parse_redis_value};

    pub struct RedisItemRepository<C: RedisClient> {
        client: C,
    }

    impl<C: RedisClient> RedisItemRepository<C> {
        pub fn new(client: C) -> Self {
            Self { client }
        }
    }

    impl<C: RedisClient> ItemRepository for RedisItemRepository<C> {
        fn create(&self, item: &Item) -> Result<(), String> {
            let redis_key = format!("item:{}", item.id);
            // ... serialization logic ...
            // Use self.client to interact with Redis
            self.client.hash_mset(redis_key, fields)
        }

        // ... other methods
    }
    ```
4.  **Register the Module**: Add your new module to `src/lib.rs` and export the trait and implementation.
    ```rust
    pub mod item;
    pub use item::{ItemRepository, RedisItemRepository};
    ```

### Testing

-   **Integration Tests**: Write integration tests that use a real Redis instance (if available) or a mock.
-   **Mocking**: For unit testing services, you don't need to test the repository implementation itself, but you should ensure the repository trait is easy to mock.

### Best Practices

-   **Abstraction**: Keep the repository trait agnostic of the underlying database technology (e.g., don't expose Redis-specific types in the trait signature).
-   **Serialization**: Handle serialization/deserialization within the repository implementation. The service layer should work with domain models, not raw JSON or database rows.
-   **Keyspace**: Use a consistent naming convention for Redis keys (e.g., `entity:id`).
-   **Atomicity**: Use Redis transactions or atomic commands where possible to ensure data consistency.
