README.md

# ExScimEcto

Ecto-based storage adapter for [ExScim](https://github.com/ExScim/ex_scim). Provides persistent storage for SCIM Users and Groups backed by any Ecto-compatible database.

## Installation

Add `ex_scim_ecto` to your dependencies:

```elixir
def deps do
  [
    {:ex_scim_ecto, "~> 0.1"}
  ]
end
```

## Configuration

### Basic setup

```elixir
config :ex_scim,
  storage_strategy: ExScimEcto.StorageAdapter,
  storage_repo: MyApp.Repo,
  user_model: MyApp.Accounts.User,
  group_model: MyApp.Groups.Group
```

### Preloading associations

```elixir
config :ex_scim,
  storage_repo: MyApp.Repo,
  user_model: {MyApp.Accounts.User, preload: [:roles, :organizations]},
  group_model: {MyApp.Groups.Group, preload: [:members]}
```

### Custom lookup key

By default, resources are looked up by `:id`. To use a different column:

```elixir
config :ex_scim,
  user_model: {MyApp.Accounts.User, lookup_key: :resource_id},
  group_model: {MyApp.Groups.Group, lookup_key: :uuid}
```

### Filter mapping

Map SCIM complex attribute paths to database columns:

```elixir
config :ex_scim,
  user_model: {MyApp.Accounts.User,
    filter_mapping: %{
      "emails.value" => :email,
      "name.givenName" => :given_name
    }}
```

For association-based filtering (e.g. filtering on a `has_many` relation):

```elixir
config :ex_scim,
  user_model: {MyApp.Accounts.User,
    preload: [:user_emails],
    filter_mapping: %{
      "emails.value" => {:assoc, :user_emails, :value},
      "emails.type" => {:assoc, :user_emails, :type}
    }}
```

### Multi-tenant scoping

Scope all queries by a tenant discriminator column:

```elixir
config :ex_scim,
  user_model: {MyApp.Accounts.User, tenant_key: :organization_id},
  group_model: {MyApp.Groups.Group, tenant_key: :organization_id}
```

### Field mapping

Map domain fields to database columns with value transformation:

```elixir
config :ex_scim,
  user_model: {MyApp.Accounts.User,
    field_mapping: %{
      active: {:status,
        fn true -> "active"; false -> "inactive" end,
        fn "active" -> true; _ -> false end}
    }}
```

Each entry is `domain_field => {db_field, to_storage_fn, from_storage_fn}`.

## Ecto schema requirements

Your Ecto schema must define a `changeset/2` function that the adapter will call for inserts and updates:

```elixir
defmodule MyApp.Accounts.User do
  use Ecto.Schema

  schema "users" do
    field :user_name, :string
    field :display_name, :string
    # ...
    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:user_name, :display_name])
    |> validate_required([:user_name])
  end
end
```