# Remote market sync proxy

This backend exposes two authenticated GET endpoints that validate query parameters, call the remote data server **server-to-server**, and return JSON. The remote API key is stored only in Laravel configuration and is **never** exposed to clients.

## Architecture

| Layer | Responsibility |
|--------|----------------|
| `RemoteMarketSyncProxyController` | Validates `date` vs `start_date`/`end_date`, enforces max range. |
| `RemoteMarketSyncService` | Builds outbound URL, `Http::` client with timeout, optional `X-API-Key`, maps upstream errors. |
| `config/remote_market.php` | Reads `REMOTE_MARKET_*` environment variables. |

## Authentication and permissions

- **Client → Laravel:** JWT (`jwt.auth`), token check (`check.token`), and route permission (`dynamic.permission`), consistent with other protected admin APIs. Clients must send a valid Bearer token (and satisfy dynamic permission rules for these routes if they are registered in your permission system).
- **Laravel → remote server:** If `REMOTE_MARKET_SYNC_API_KEY` is set, it is sent as header `X-API-Key: <key>` on outbound requests only.

### `dynamic.permission` and the `route_apis` table

`DynamicPermissionMiddleware` loads `route_apis` using **`Route::currentRouteName()`**, not arbitrary path strings. The **`url`** column must equal the Laravel **route name** (here: `syncDayend`, `syncIndexStatistics`). It also filters by **`method`** (e.g. `GET`).

If `url` does not match the route name, the API returns **403** with `API is not created in Database.`

Do **not** store a different `url` than the route’s `->name(...)` (for example `market/syncDayend` will not match if the route name is `syncDayend`).

Example when you already have a row and only need to fix `url`:

```sql
UPDATE route_apis SET url = 'syncDayend' WHERE api_id = <your_api_id>;
```

**Primary key:** `route_apis.api_id` is unique. Insert new APIs without forcing an id that already exists; then map **`route_api_assign_to_roles`** for the new `api_id`.

After the API row exists, the user still needs **`route_api_assign_to_roles`** with **`rar_activity_flag = 'A'`** for their **`user_id`** and that **`api_id`**, or the middleware returns **403** `API Unauthorized. You do not have permission to access this resource.`

Set these in `.env` (see `.env.example` for a template).

| Variable | Description |
|----------|-------------|
| `REMOTE_MARKET_API_BASE_URL` | Remote base URL **without** a trailing slash (e.g. `https://your-data-server.example.com`). Must match the server that exposes `/api/sync/...`. |
| `REMOTE_MARKET_SYNC_API_KEY` | Same value as the remote server’s `SYNC_API_KEY`, if that server requires authentication. Leave empty if the remote does not use a key. |
| `REMOTE_MARKET_HTTP_TIMEOUT` | Request timeout in seconds (default: `30`). |
| `REMOTE_MARKET_HTTP_CONNECT_TIMEOUT` | Connect timeout in seconds (default: `10`). |
| `REMOTE_MARKET_MAX_RANGE_DAYS` | Maximum **inclusive** calendar days for `start_date` … `end_date` (default: `366`). |

After changing `.env`, run `php artisan config:clear` if you use config caching in deployment.

## Laravel API routes

Routes are registered in `routes/api.php` with **`Route::match(['get'], ...)`** (GET only), middleware **`jwt.auth`**, **`check.token`**, **`dynamic.permission`**. All paths use the default **`/api`** prefix.

| Method | Path | Route name (`route_apis.url` must match) | Controller | Proxies to (remote) |
|--------|------|------------------------------------------|--------------|---------------------|
| `GET` | `/api/syncDayend` | `syncDayend` | `RemoteMarketSyncProxyController@dayEnd` | `GET {base}/api/sync/market-day-end-data` |
| `GET` | `/api/syncIndexStatistics` | `syncIndexStatistics` | `RemoteMarketSyncProxyController@indexStatistics` | `GET {base}/api/sync/market-index-statistic-data` |

`{base}` is `REMOTE_MARKET_API_BASE_URL` with no trailing slash.

## Query parameters (client must match remote rules)

Provide **either**:

1. **Single day:** `date=YYYY-MM-DD`  
   Do **not** send `start_date` or `end_date`.

2. **Inclusive range:** `start_date=YYYY-MM-DD` **and** `end_date=YYYY-MM-DD` with `end_date` ≥ `start_date`.  
   Do **not** send `date`.

If the inclusive span exceeds `REMOTE_MARKET_MAX_RANGE_DAYS`, Laravel responds with `422` and `error: range_too_large`.

## Example outbound URLs (what Laravel calls)

Replace `{base}` with your configured base URL.

**Single day**

```http
GET {base}/api/sync/market-day-end-data?date=2026-05-12
GET {base}/api/sync/market-index-statistic-data?date=2026-05-12
```

**Range**

```http
GET {base}/api/sync/market-day-end-data?start_date=2026-05-01&end_date=2026-05-12
GET {base}/api/sync/market-index-statistic-data?start_date=2026-05-01&end_date=2026-05-12
```

## Example client requests (this Laravel app)

Use your normal JWT header (`Authorization: Bearer <token>`).

```http
GET /api/syncDayend?date=2026-05-12
GET /api/syncIndexStatistics?start_date=2026-05-01&end_date=2026-05-12
```

`dynamic.permission` also expects the same user fields as other protected routes (e.g. `users_id`, `name`, `email`, `role`, `post`) when your client sends them on the query string.

### cURL examples (Laravel proxy — use your host and JWT)

Replace `https://your-laravel-host.example.com` with your `APP_URL` (or public API base). Replace `YOUR_JWT_TOKEN` with a valid Bearer token.

**1. Day-end — single `date`**

```bash
curl -sS -G 'https://your-laravel-host.example.com/api/syncDayend' \
  --data-urlencode 'date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
```

**2. Index statistics — single `date`**

```bash
curl -sS -G 'https://your-laravel-host.example.com/api/syncIndexStatistics' \
  --data-urlencode 'date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
```

**3. Day-end — inclusive range (`start_date` + `end_date`)**

```bash
curl -sS -G 'https://your-laravel-host.example.com/api/syncDayend' \
  --data-urlencode 'start_date=2026-05-01' \
  --data-urlencode 'end_date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
```

**4. Index statistics — inclusive range (`start_date` + `end_date`)**

```bash
curl -sS -G 'https://your-laravel-host.example.com/api/syncIndexStatistics' \
  --data-urlencode 'start_date=2026-05-01' \
  --data-urlencode 'end_date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
```

### cURL examples (remote data server — debugging only)

These hit the **remote** host directly (not through Laravel). Use only if you need to verify the upstream API; include `X-API-Key` when the remote has `SYNC_API_KEY` set.

Replace `{base}` with `REMOTE_MARKET_API_BASE_URL` (no trailing slash). Replace `YOUR_REMOTE_SYNC_API_KEY` if required.

**Remote — single `date` (day-end)**

```bash
curl -sS -G '{base}/api/sync/market-day-end-data' \
  --data-urlencode 'date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'X-API-Key: YOUR_REMOTE_SYNC_API_KEY'
```

**Remote — single `date` (index statistics)**

```bash
curl -sS -G '{base}/api/sync/market-index-statistic-data' \
  --data-urlencode 'date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'X-API-Key: YOUR_REMOTE_SYNC_API_KEY'
```

**Remote — range (day-end)**

```bash
curl -sS -G '{base}/api/sync/market-day-end-data' \
  --data-urlencode 'start_date=2026-05-01' \
  --data-urlencode 'end_date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'X-API-Key: YOUR_REMOTE_SYNC_API_KEY'
```

**Remote — range (index statistics)**

```bash
curl -sS -G '{base}/api/sync/market-index-statistic-data' \
  --data-urlencode 'start_date=2026-05-01' \
  --data-urlencode 'end_date=2026-05-12' \
  -H 'Accept: application/json' \
  -H 'X-API-Key: YOUR_REMOTE_SYNC_API_KEY'
```

On Windows PowerShell you can run the same commands in **Git Bash** or replace line endings with backticks per PowerShell rules; alternatively use `curl.exe` with a single-line URL and `-H` flags.

## Response behaviour

- **Success:** HTTP status from the remote is preserved for successful responses; body is the remote JSON payload.
- **Empty `data`:** If the remote returns success with `"data": []`, the proxy overwrites **`message`**: single-day requests use `No data found on {date}.`; range requests use `No data found from {start_date} to {end_date}.`.
- **Local validation failure:** `422` with `message` and `errors` (Laravel validation structure).
- **Remote `401`:** `401` with `error: remote_unauthorized` and `details` from the remote when JSON is present.
- **Remote `422`:** `422` with `error: remote_validation_error` and `details`.
- **Remote `5xx`:** `502` with `error: remote_server_error` and `details` (avoids masking upstream failures as local `500`).
- **Connection / timeout failure:** `503` with `error: remote_unreachable`. The proxy could not open a connection to **`REMOTE_MARKET_API_BASE_URL`** (nothing listening, wrong host/port, firewall, or DNS). Typical local fix: start the **data server** that exposes `/api/sync/market-*` on the URL you configured (e.g. if `.env` has `http://127.0.0.1:8000`, run that app on port **8000** while the proxy may run on **8001**). With **`APP_DEBUG=true`**, the JSON may include a **`debug`** object (`attempted_url`, `exception`) for troubleshooting; check **`storage/logs/laravel.log`** for `Remote market sync: connection failed`.
- **Missing `REMOTE_MARKET_API_BASE_URL`:** `503` with `error: configuration_error`.

## Source files

- `routes/api.php` — two `GET` routes: `syncDayend`, `syncIndexStatistics`; middleware `jwt.auth`, `check.token`, `dynamic.permission`
- `app/Http/Controllers/RemoteMarketSyncProxyController.php`
- `app/Services/RemoteMarketSyncService.php`
- `config/remote_market.php`
