# تطبیق سایت عمومی با تغییرات API برنچ `backend-audit`

> ✅ **حل‌شده (Path A) — هیچ تغییری در `nobat724_front` لازم نیست.**
> مشکل rotation با **نرم‌کردن backend** حل شد: در `clinicpro` (commit روی `backend-audit`) چرخش/باطل‌سازی single-use از `/oauth/token/refresh` برداشته شد و توکن refresh دوباره قابل‌استفاده شد (فقط چک کاربر معلق `status!=1` باقی ماند). پس `lib/serverToken.js` بدون تغییر کار می‌کند.
> تنها نکته‌ی باقی‌مانده **اختیاری** است: ۵۰-cap نظرات (وظیفه ۲ پایین) — اگر ۵۰ نظر کافی است، کاری لازم نیست.

## پروژه

`nobat724_front` (سایت عمومی). پرامپت همتا برای Admin SPA: `clinicpro/.claude/prompt/admin-spa-adapt-backend-audit.md`. منبع تغییرات: برنچ `backend-audit` در `clinicpro` (۳۳ commit).

## زمینه

ممیزی بک‌اند چند endpoint را تغییر داد. مقدارهای wire کدهای خطا حفظ شده‌اند، ولی **یک تغییر رفتارِ شکننده** برای سایت عمومی وجود دارد (rotation توکن refresh) و یک تغییر کم‌اهمیت (صفحه‌بندی نظرات). این پرامپت مصرف‌کننده‌های `nobat724_front` را اصلاح می‌کند.

## جدول تغییرات API مرتبط با سایت عمومی

| Endpoint | تغییر | مصرف در nobat724 | شدت |
|---|---|---|---|
| `POST /oauth/token/refresh` | refresh_token اکنون **یک‌بارمصرف** است و در هر فراخوانی **چرخش** می‌کند؛ کاربر معلق `401` | `lib/serverToken.js` | 🔴 **شکننده** |
| `GET /api/v1/comments/{uuid}` | `data.meta` افزوده شد، پیش‌فرض **۵۰ نظر** (قبلاً همه) | `app/doctor/[slug]/page.js`, `services/response.js` `getDoctorComments` | 🟡 کم |
| `GET /api/v1/insurance/{id}`, `GET /clinic-pro/doctor-address/{id}`, `appointment-settings/{...}` | حالا owner/admin (`403`) | مصرف نمی‌شود | ⚪ بدون اثر |
| کدهای خطای legacy (M21) | مقدار wire بدون تغییر | — | ⚪ بدون اثر |

## 🔴 مشکل اصلی: rotation توکن refresh (`POST /oauth/token/refresh`)

### رفتار جدید backend
هر فراخوانی `/oauth/token/refresh`:
1. توکن ارائه‌شده را **باطل** می‌کند (single-use)،
2. یک جفت `access_token` + **`refresh_token` جدید** صادر می‌کند،
3. کاربر با `status != 1` → `401`.

پاسخ:
```json
{ "access_token": "…", "refresh_token": "<توکن جدید — با قبلی فرق دارد>", "token_type": "Bearer", "expires_in": 900, "refresh_token_expires_in": 2592000 }
```

### وضعیت فعلی nobat724 (کد واقعی — `lib/serverToken.js`)

```js
export async function getServerAccessToken() {
  const cookieStore = await cookies();
  const refreshToken = cookieStore.get("refresh_token")?.value;
  if (!refreshToken) return null;
  try {
    const res = await axiosInstance.post(
      `${process.env.NEXT_PUBLIC_API_URL}/oauth/token/refresh`,
      { refresh_token: refreshToken },
      { headers: { "Content-Type": "application/json", Authorization: "" } }
    );
    return res.data?.access_token ?? null;   // ⚠️ فقط access_token خوانده می‌شود
  } catch { return null; }
}
```

### چرا می‌شکند
- این تابع **در هر بار رندر صفحه** صدا زده می‌شود (مثلاً `app/dashboard/page.js`).
- `refresh_token` چرخش‌یافته‌ی جدید را **ذخیره نمی‌کند** و توکن قدیمیِ کوکی پس از اولین refresh **باطل** شده است.
- نتیجه: refresh اول OK → ناوبری بعدی همان کوکی باطل را می‌فرستد → `401` → `null` → **redirect به login** (خروج عملی کاربر).
- **محدودیت Next.js:** `getServerAccessToken()` از یک **Server Component** صدا زده می‌شود؛ Server Componentها **نمی‌توانند کوکی ست کنند** (فقط Server Action / Route Handler / middleware می‌توانند). پس ذخیره‌ی توکن چرخش‌یافته در همین تابع ممکن نیست.

### وظیفه ۱ — حل rotation (یکی از دو مسیر؛ مسیر A توصیه می‌شود)

> این یک **تصمیم cross-repo** است. قبل از پیاده‌سازی، با تیم بک‌اند هماهنگ کن.

**مسیر A (توصیه‌شده — تغییر در backend):**
چون سایت در هر رندر refresh می‌زند، single-use rotation با این معماری ناسازگار است. بهترین کار: در `clinicpro` `AuthController::refreshToken` **بخشِ چرخش/باطل‌سازی را بردار** و فقط **چک status** (کاربر معلق `401`) را نگه‌دار. در این حالت `serverToken.js` نیازی به تغییر ندارد. (یک پرامپت backend جدا برای این کار بساز.)

**مسیر B (تغییر در nobat724 — اگر rotation باید بماند):**
refresh را به جایی منتقل کن که **اجازه‌ی ست‌کردن کوکی دارد**:
- یک **Route Handler** (`app/api/refresh/route.js`) یا **middleware** که `/oauth/token/refresh` را صدا بزند و **`res.data.refresh_token` و `access_token` جدید را در کوکی بنویسد**، سپس صفحات به‌جای فراخوانی مستقیم، از این مسیر استفاده کنند.
- نمونه (Route Handler):
```js
// app/api/refresh/route.js
import { cookies } from "next/headers";
export async function POST() {
  const jar = await cookies();
  const rt = jar.get("refresh_token")?.value;
  if (!rt) return Response.json({ ok: false }, { status: 401 });
  const r = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/oauth/token/refresh`, {
    method: "POST", headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: rt }),
  });
  if (!r.ok) return Response.json({ ok: false }, { status: 401 });
  const d = await r.json();
  jar.set("access_token", d.access_token, { httpOnly: true, secure: true, sameSite: "lax", maxAge: d.expires_in });
  jar.set("refresh_token", d.refresh_token, { httpOnly: true, secure: true, sameSite: "lax", maxAge: d.refresh_token_expires_in }); // ← توکن چرخش‌یافته
  return Response.json({ ok: true, access_token: d.access_token });
}
```
- بعد `getServerAccessToken` (در Server Component، read-only) فقط کوکی `access_token` معتبر فعلی را بخواند؛ تجدید توسط middleware/route قبل از رندر انجام شود.

> **مهم:** هر کجای دیگر `nobat724` که توکن refresh ذخیره/استفاده می‌شود را هم بررسی کن (مثلاً جریان login که کوکی‌ها را ست می‌کند) تا توکن چرخش‌یافته‌ی جدید همیشه جایگزین قدیمی شود.

## 🟡 وظیفه ۲ — صفحه‌بندی نظرات (`GET /api/v1/comments/{uuid}`)

### وضعیت فعلی (`app/doctor/[slug]/page.js`)
```js
const resComments = await fetch(`${API_URL}/api/v1/comments/${doctor.uuid}`, { cache: "no-store" });
const jsonComments = resComments.ok ? await resComments.json() : null;
comments = jsonComments?.data?.data;   // آرایه — هنوز کار می‌کند
```

### تغییر
پاسخ حالا `{ data: { data: [...], meta: { totalRecords, totalPages, currentPage } } }` است و **پیش‌فرض ۵۰ نظر** برمی‌گرداند (قبلاً همه). `data.data` (آرایه) دست‌نخورده → کد فعلی **نمی‌شکند**، فقط حداکثر ۵۰ نظر نشان می‌دهد.

### وظیفه
- اگر برای صفحه‌ی پزشک ۵۰ نظر کافی است (به‌علاوه‌ی schema/Review)، **هیچ تغییری لازم نیست** (فقط آگاه باش).
- اگر همه‌ی نظرات لازم است: یا `?limit=100` بفرست، یا «نمایش بیشتر»/صفحه‌بندی با `data.meta.totalPages` اضافه کن. `getDoctorComments` در `services/response.js` را هم در صورت نیاز با پارامتر `page`/`limit` تطبیق بده.
- اگر برای SEO/`Review` schema از تعداد کل نظر استفاده می‌کنی، آن را از `data.meta.totalRecords` بخوان (نه `length` آرایه‌ی ۵۰‌تایی).

## نکات مهم

- `lib/serverToken.js` در Server Component اجرا می‌شود → نمی‌تواند کوکی ست کند؛ ست‌کردن کوکی فقط در Route Handler/Server Action/middleware. (مسیر A این مشکل را کلاً حذف می‌کند.)
- `clinic-pro-tauri` تحت تأثیر rotation **نیست** چون از `/oauth/token` (grant_type=refresh_token، OAuth bundle) استفاده می‌کند نه `/oauth/token/refresh`.
- کدهای خطا (M21) مقدار wire ثابت دارند؛ هر منطقی که روی رشته‌ی `code` switch می‌کند سالم است.
- endpointهای owner-only جدید (`insurance/{id}`, `doctor-address/{id}`, `appointment-settings/*`) توسط سایت عمومی مصرف نمی‌شوند → بدون اثر.
- بعد از تغییر: `npm run lint` و `npm run build`.
```
