Files
nobat724_front/.claude/prompt/adapt-backend-audit-api.md

119 lines
9.3 KiB
Markdown

# تطبیق سایت عمومی با تغییرات 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`.
```