feat(api): add adaptation for backend-audit changes and update comment pagination
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# تطبیق سایت عمومی با تغییرات 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`.
|
||||
```
|
||||
Reference in New Issue
Block a user