# API Contract

Base path:

```text
/api/v1/admin/homepage
```

تمام endpointها نیازمند session معتبر ادمین و مجوز مرتبط هستند.

## Endpointها

| Method | Path | Permission | کاربرد |
| --- | --- | --- | --- |
| `GET` | `/api/v1/admin/homepage` | `homepage.view` | دریافت پیش‌نویس کامل |
| `PUT` | `/api/v1/admin/homepage` | `homepage.edit` | جایگزینی اتمیک پیش‌نویس |
| `POST` | `/api/v1/admin/homepage/validate` | `homepage.edit` | validation بدون ذخیره |
| `POST` | `/api/v1/admin/homepage/publish` | `homepage.publish` | انتشار پیش‌نویس فعلی |

به دلیل محدودیت حداکثر ۳۰ سکشن، ذخیره کل سند در یک درخواست ساده‌تر و مطمئن‌تر از CRUD جداگانه برای هر سکشن است.

## سند قابل ویرایش

```json
{
  "schemaVersion": 1,
  "revision": "home_draft_43",
  "seo": {
    "title": "فروشگاه اینترنتی آواتار",
    "description": "خرید آنلاین محصولات",
    "canonical": "https://shop.example.com/",
    "robots": "index,follow"
  },
  "sections": []
}
```

`schemaVersion` و `revision` اجباری هستند. `revision` برای جلوگیری از overwrite هم‌زمان استفاده می‌شود.

`generatedAt`، `delivery` و `sectionRevision` محتوای قابل ویرایش نیستند و بک‌اند آن‌ها را هنگام انتشار برای transport عمومی تولید می‌کند.

## دریافت پیش‌نویس

```http
GET /api/v1/admin/homepage
Accept: application/json
```

پاسخ از envelope استاندارد `{ status, message, data }` استفاده می‌کند. مقدار `data` دقیقاً یک `HomepageDocument` مطابق [نمونه کامل](./examples/homepage.document.json) است.

Header پاسخ:

```http
ETag: "home_draft_43"
Cache-Control: private, no-store
```

تمام سکشن‌ها در پاسخ ادمین دارای `data` کامل هستند. تقسیم `inline/stream` فقط هنگام ساخت پاسخ عمومی storefront انجام می‌شود.

## ذخیره پیش‌نویس

```http
PUT /api/v1/admin/homepage
Content-Type: application/json
Accept: application/json
If-Match: "home_draft_43"
```

Body همان ساختار `homepage.document.json` است. بک‌اند باید:

1. envelope، SEO و تمام سکشن‌ها را validate کند.
2. شناسه سکشن‌ها را یکتا نگه دارد.
3. اطلاعات تجاری محصول را از دیتابیس refresh کند.
4. کل سند را در یک transaction ذخیره کند.
5. revision جدید برگرداند.

پاسخ موفق از همان envelope استفاده می‌کند و سند ذخیره‌شده را با `revision` جدید در `data` برمی‌گرداند.

## اعتبارسنجی

```http
POST /api/v1/admin/homepage/validate
Content-Type: application/json
Accept: application/json
```

Body همان سند کامل صفحه است.

پاسخ موفق:

```json
{
  "status": true,
  "message": "Valid",
  "data": {
    "valid": true,
    "errors": []
  }
}
```

پاسخ نامعتبر با status کد `422` برگردانده می‌شود. مسیر خطا باید با ساختار JSON هماهنگ باشد؛ مانند `sections.2.data.products`.

## انتشار

```http
POST /api/v1/admin/homepage/publish
Content-Type: application/json
Accept: application/json
If-Match: "home_draft_44"

{
  "revision": "home_draft_44"
}
```

```json
{
  "status": true,
  "message": "Published",
  "data": {
    "revision": "home_2026_09_13_00044",
    "publishedAt": "2026-09-13T11:45:00+03:30"
  }
}
```

انتشار باید اتمیک باشد. پس از انتشار:

- revision عمومی تغییر کند.
- `generatedAt` با زمان انتشار تولید شود.
- payload عمومی `GET /api/v1/home/init` ساخته شود.
- کش tagهای `home` و `home:section:{id}` invalidate شود.

## خطاها

| Status | کاربرد |
| --- | --- |
| `401` | session نامعتبر |
| `403` | مجوز ناکافی |
| `409` | revision قدیمی یا تعارض ویرایش؛ `errors[0].code` یکی از `if_match_invalid`، `stale_revision`، `revision_mismatch` است و `data.currentRevision` (و هدر `ETag`) revision فعلی را برمی‌گرداند |
| `422` | سند یا سکشن نامعتبر |
| `500` | خطای داخلی با پیام عمومی |

فرمت خطای validation:

```json
{
  "status": false,
  "message": "Validation failed",
  "errors": [
    {
      "path": "sections.2.data.products",
      "code": "too_small",
      "message": "حداقل یک محصول الزامی است"
    }
  ]
}
```

## قواعد امنیتی

- mutationها باید CSRF protection داشته باشند.
- مجوز در بک‌اند بررسی شود؛ مخفی‌بودن دکمه کافی نیست.
- HTML، CSS، JavaScript، نام component و module path پذیرفته نشود.
- لینک‌ها فقط path داخلی معتبر باشند.
- media فقط URLهای HTTP/HTTPS از hostهای مجاز باشد.
- payload ادمین `private, no-store` باشد.
- جزئیات exception و query دیتابیس در پاسخ قرار نگیرد.
