# Rating & Comments API

> **Prefix:** `/api/v1/rate`, `/api/v1/comment`, `/api/v1/like`

---

## POST `/api/v1/rate`

Submit a multi-dimensional rating for a doctor. Upsert — re-submitting overwrites the user's previous rating.

**Permission:** `AUTH`

> **Eligibility rule:** The user must have had a **confirmed** appointment (`status = confirmed`) with this doctor whose `slot_start` falls within the **last 30 days**. Otherwise the request is rejected with `403 ERR_RATING_NOT_ELIGIBLE`. Use [`GET /api/v1/rate/{doctorUuid}/eligibility`](#get-apiv1ratedoctoruuideligibility) to check before showing the rating UI.

### Request Body (`application/json`)
Five dimensions, each an integer percentage `0–100`:
```json
{
  "doctor_uuid": "550e8400-...",
  "waiting_time_at_clinic": 80,
  "accuracy_of_diagnosis": 100,
  "doctor_behavior": 100,
  "clinic_cleanliness": 60,
  "doctor_expertise": 100
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `doctor_uuid` | string (UUID) | ✅ | Must exist |
| `waiting_time_at_clinic` | integer | ✅ | 0–100 |
| `accuracy_of_diagnosis` | integer | ✅ | 0–100 |
| `doctor_behavior` | integer | ✅ | 0–100 |
| `clinic_cleanliness` | integer | ✅ | 0–100 |
| `doctor_expertise` | integer | ✅ | 0–100 |

### Response `201` / `200`
Returns the **updated aggregate** for the doctor (same shape as `GET /api/v1/rate/{doctorUuid}`):
```json
{
  "success": true,
  "data": {
    "data": {
      "point": 4.4,
      "satisfaction": 88,
      "averages": [
        { "name": "waiting_time_at_clinic", "label": "زمان انتظار در مطب", "progress": 80 },
        { "name": "accuracy_of_diagnosis",  "label": "تشخیص درست",        "progress": 100 },
        { "name": "doctor_behavior",        "label": "برخورد مناسب پزشک",  "progress": 100 },
        { "name": "clinic_cleanliness",     "label": "نظافت مطب",         "progress": 60 },
        { "name": "doctor_expertise",       "label": "مهارت پزشک",        "progress": 100 }
      ]
    }
  }
}
```
> Note: response is double-nested (`data.data`) — `success(['data' => $aggregate])`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_RATING_NOT_ELIGIBLE` | 403 | No confirmed appointment with this doctor in the last 30 days |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
| `ERR_VALIDATION_001` | 422 | A dimension is out of the 0–100 range |

---

## GET `/api/v1/rate/{doctorUuid}`

Get the aggregate (multi-dimensional) rating for a doctor: overall star point, satisfaction percent, and per-dimension averages.

**Permission:** `PUBLIC`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `doctorUuid` | string (UUID) | Doctor UUID |

### Response `200`
```json
{
  "success": true,
  "data": {
    "data": {
      "point": 4.4,
      "satisfaction": 88,
      "averages": [
        { "name": "waiting_time_at_clinic", "label": "زمان انتظار در مطب", "progress": 80 },
        { "name": "accuracy_of_diagnosis",  "label": "تشخیص درست",        "progress": 100 },
        { "name": "doctor_behavior",        "label": "برخورد مناسب پزشک",  "progress": 100 },
        { "name": "clinic_cleanliness",     "label": "نظافت مطب",         "progress": 60 },
        { "name": "doctor_expertise",       "label": "مهارت پزشک",        "progress": 100 }
      ]
    }
  }
}
```
- `point`: overall rating on a 0–5 scale (`satisfaction / 20`).
- `satisfaction`: mean of all dimensions, percent `0–100`.
- `averages[].progress`: per-dimension mean, percent `0–100`.
- If the doctor has no ratings: `point=0`, `satisfaction=0`, every `progress=0`.
- Response is double-nested (`data.data`).

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## GET `/api/v1/rate/{doctorUuid}/eligibility`

Whether the **current authenticated user** is allowed to rate/comment on this doctor — i.e. had a confirmed appointment with them in the last 30 days. Intended for the public site to conditionally show the "submit review" UI.

**Permission:** `AUTH` (`IS_AUTHENTICATED_FULLY`)

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `doctorUuid` | string (UUID) | Doctor UUID |

### Response `200`
```json
{
  "success": true,
  "data": {
    "eligible": true
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## POST `/api/v1/comment`

Submit a comment/review for a doctor.

**Permission:** `AUTH`

> Comments require admin approval before appearing publicly.
>
> **Eligibility rule:** Same as `POST /api/v1/rate` — the user must have had a **confirmed** appointment with this doctor within the **last 30 days**, otherwise `403 ERR_RATING_NOT_ELIGIBLE`.

### Request Body (`application/json`)
```json
{
  "doctor_uuid": "550e8400-...",
  "comment": "پزشک بسیار مؤدب و متخصص بودند",
  "parent": null
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `doctor_uuid` | string (UUID) | ✅ | Must exist |
| `comment` | string | ✅ | Non-empty |
| `parent` | string (UUID) \| null | ❌ | If set, this comment is a reply to the parent comment |

### Response `201`
Returns the created comment in the **rich shape** (see `GET /comments` below). New comments are `pending` until an admin approves them, so they will not appear in the public list yet.

**Comment Status Values:** `pending` (awaiting review) · `approved` (public) · `rejected`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_RATING_NOT_ELIGIBLE` | 403 | No confirmed appointment with this doctor in the last 30 days |
| `ERR_NOT_FOUND_001` | 404 | Doctor (or parent comment) not found |
| `ERR_VALIDATION_002` | 422 | Comment text empty |

---

## GET `/api/v1/comments/{doctorUuid}`

Get approved **root** comments for a doctor (replies are nested under each root via `replies`).

**Permission:** `PUBLIC`

> **صفحه‌بندی:** `?page` و `?limit` (پیش‌فرض ۵۰، حداکثر ۱۰۰)؛ پاسخ شامل `data.meta` (`totalRecords`/`totalPages`/`currentPage`) است. آرایه‌ی نظرات همچنان در `data.data` است. همین صفحه‌بندی روی `GET /api/v1/admin/comments/pending` هم اعمال می‌شود.

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `doctorUuid` | string (UUID) | Doctor UUID |

### Response `200`
Response is double-nested (`data.data`). Each item:
```json
{
  "success": true,
  "data": {
    "data": [
      {
        "uuid": "...",
        "comment": "پزشک بسیار مؤدب...",
        "created": 1717000000,
        "parent": null,
        "author": { "real_name": "میثم امیری", "picture": [] },
        "like_status": {
          "like_count": 6,
          "dislike_count": 1,
          "current_user_like": { "like": false, "dislike": false }
        },
        "replies": [
          {
            "uuid": "...",
            "comment": "پاسخ ...",
            "created": 1717000500,
            "parent": "<root-uuid>",
            "author": { "real_name": "امیر حبیبی", "picture": [] },
            "like_status": { "like_count": 0, "dislike_count": 0, "current_user_like": { "like": false, "dislike": false } },
            "replies": []
          }
        ]
      }
    ]
  }
}
```
- `comment` (not `body`); `created` (not `created_at`); both Unix seconds.
- `author.real_name` from the user (falls back to «کاربر نوبت‌۷۲۴» if unset). `author.picture` is always `[]` (no user avatar field) — frontend uses a default image.
- `current_user_like` is always `{false,false}` on this public endpoint (no token is processed); the real per-user state comes from the `POST /like` response — keep the UI optimistic.
- Only `approved` comments/replies are returned.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## DELETE `/api/v1/comment/{uuid}`

Delete a comment.

**Permission:** `AUTH` — must be the comment author or `ROLE_ADMIN`

### Response `200`
```json
{ "success": true, "data": { "message": "نظر حذف شد" } }
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the author |
| `ERR_NOT_FOUND_001` | 404 | Comment not found |

---

## GET `/api/v1/admin/comments/pending`

Get all pending comments waiting for review.

**Permission:** `ROLE_ADMIN`

### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "body": "...",
      "doctor": { "uuid": "...", "title": "علی احمدی" },
      "user": { "uuid": "...", "real_name": "..." },
      "status": "pending",
      "created_at": 1717000000
    }
  ]
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |

---

## POST `/api/v1/admin/comment/{uuid}/approve`

Approve a pending comment (makes it public).

**Permission:** `ROLE_ADMIN`

### Response `200`
Updated comment object with `status: "approved"`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Comment not found |

---

## POST `/api/v1/admin/comment/{uuid}/reject`

Reject a pending comment.

**Permission:** `ROLE_ADMIN`

### Response `200`
Updated comment object with `status: "rejected"`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Comment not found |

---

## POST `/api/v1/like/{commentUuid}`

Cast a like or dislike on a comment. Toggling logic:
- Same vote sent again → vote is **removed**.
- Opposite vote sent → vote is **replaced** (e.g. like → dislike).
- No existing vote → vote is **added**.

**Permission:** `AUTH`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `commentUuid` | string (UUID) | Comment UUID |

### Request Body (`application/json`)
```json
{ "value": 1 }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `value` | integer | ❌ (default 1) | `1` = like, `-1` = dislike |

### Response `200`
```json
{
  "success": true,
  "data": {
    "like_count": 6,
    "dislike_count": 1,
    "current_user_like": { "like": true, "dislike": false }
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_NOT_FOUND_001` | 404 | Comment not found |
