# Phase 5 — Mobile Gateway + Transform Engine

**Duration:** Weeks 13–14
**Status:** ✅ Complete — 2026-04-26
**Goal:** Mobile-first API surface with compact responses, push notifications, and
         a config-driven transformation engine replacing hardcoded mappers.

---

## Deliverable

1. `gateway_mobile` with `/m/v1/` routes returning compact JSON
2. Push notification dispatch (FCM + APNS) on async completion
3. `mw_transform` schema registry and DB-stored mapping rules (replace Phase 1 hardcoding)
4. API versioning via `Accept` header for all gateways

---

## Tasks

### 1. gateway_mobile — Compact REST API

**Dependencies to add:** `pigeon` (FCM/APNS)

Mobile responses omit hypermedia, use integer status codes, and are designed for low-bandwidth:

```elixir
# Compact response — no nesting, snake_case, no null fields
%{
  "tx_id" => "abc123",
  "status" => "approved",
  "amount" => 10000,  # in minor units
  "currency" => "USD",
  "ref" => "CBS-456"
}
```

vs REST API response which includes full metadata.

Device registration:

```
POST /m/v1/devices
  body: {"device_token": "...", "platform": "ios"|"android"}
  → stores in device_registrations table, linked to user_id from JWT
```

### 2. Push Notifications

When an async job completes (file ingestion, async banking callback):

```elixir
defmodule GatewayMobile.Push do
  def notify_user(user_id, payload) do
    user_id
    |> GatewayMobile.DeviceRegistry.get_tokens()
    |> Enum.each(fn
      %{platform: :ios, token: token} ->
        GatewayMobile.Push.APNS.send(token, payload)
      %{platform: :android, token: token} ->
        GatewayMobile.Push.FCM.send(token, payload)
    end)
  end
end

defmodule GatewayMobile.Push.FCM do
  def send(token, %{title: title, body: body} = payload) do
    notification = Pigeon.FCM.Notification.new(token, %{"title" => title}, %{"body" => body})
    Pigeon.FCM.push(notification)
  end
end
```

PubSub subscriber in `gateway_mobile` listens for `"jobs:completed"` and dispatches push.

```
DB migration: create table device_registrations (
  id, user_id, platform, device_token, active, registered_at, last_seen_at
)
```

### 3. mw_transform — Config-driven Mapping Engine

Replace Phase 1 hardcoded mapper with DB-stored, ETS-cached mapping rules.

```
DB migration: create table transform_rules (
  id, message_type, direction (inbound|outbound),
  source_field, target_field, transform_fn (cast|rename|default|drop),
  active, priority
)

DB migration: create table schema_registry (
  id, message_type, version, json_schema, active
)
```

```elixir
defmodule MwTransform.Mapper do
  def to_canonical(message_type, params) do
    rules = InfraCache.EtsCache.get(:transform_rules, {message_type, :inbound})
      || load_and_cache(message_type, :inbound)

    Enum.reduce(rules, %{}, fn rule, acc ->
      apply_rule(rule, params, acc)
    end)
    |> then(&struct(MwKernel.Message, &1))
  end

  defp apply_rule(%{transform_fn: "rename", source_field: src, target_field: tgt}, params, acc) do
    Map.put(acc, tgt, Map.get(params, src))
  end

  defp apply_rule(%{transform_fn: "cast_integer", source_field: src, target_field: tgt}, params, acc) do
    Map.put(acc, tgt, String.to_integer(Map.get(params, src, "0")))
  end
  # ... etc
end
```

Schema validation via `ex_json_schema`:

```elixir
defmodule MwTransform.Validator do
  def validate(message_type, params) do
    schema = MwTransform.SchemaRegistry.get(message_type)
    case ExJsonSchema.Validator.validate(schema, params) do
      :ok -> {:ok, params}
      {:error, errors} -> {:error, errors}
    end
  end
end
```

### 4. API Versioning Strategy

All gateways support `Accept` header versioning in addition to URL versioning:

```
Accept: application/vnd.mwcore.v2+json
```

`mw_router` reads the version from context and selects the correct transform rule set:

```elixir
# transform_rules has version column
rules = InfraCache.EtsCache.get(:transform_rules, {message_type, :inbound, version})
```

URL versioning (`/api/v1/` vs `/api/v2/`) continues to work for backward compatibility.

---

## Acceptance Criteria

- [ ] `POST /m/v1/transactions` returns compact JSON (fewer fields than REST API)
- [ ] Device registered via `POST /m/v1/devices` receives FCM push on async job completion
- [ ] iOS device receives APNS push notification
- [ ] Transform rules editable via admin UI (gateway_web) — changes take effect immediately
- [ ] Schema validation rejects malformed payload with 422 and field-level errors
- [ ] `Accept: application/vnd.mwcore.v2+json` routes to v2 transform rules
- [ ] Pigeon gracefully handles invalid device tokens (removes from registry)
