diff --git a/.claude/prompt/blog-frontend-sync-and-default-cover.md b/.claude/prompt/blog-frontend-sync-and-default-cover.md new file mode 100644 index 0000000..fcd5032 --- /dev/null +++ b/.claude/prompt/blog-frontend-sync-and-default-cover.md @@ -0,0 +1,360 @@ +# سینک کامل صفحهٔ بلاگ با بک‌اند + تصویر پیش‌فرض برند + باطل‌سازی کش + +## پروژه + +`nobat724_front` (سایت عمومی) + +پرامپت همتا (cross-repo، **اول اجرا شود**): `clinicpro/.claude/prompt/blog-admin-edit-and-dark-mode.md` — اندپوینت ادمین و فراخوانندهٔ webhook آنجا ساخته می‌شود؛ **مصرف‌کنندهٔ** webhook اینجاست. + +## زمینه + +کاربر گزارش داده صفحهٔ بلاگ سایت (`https://nobat724.com/blog/`) همهٔ اطلاعاتی را که در پنل ادمین مدیریت می‌شود نشان نمی‌دهد، و مقالهٔ بدون تصویر شاخص با تصویر پیش‌فرض نامناسب نمایش داده می‌شود. + +بررسی کد نشان می‌دهد بک‌اند در `Blog::toArray()` این فیلدها را برمی‌گرداند: + +``` +uuid, title, slug, summary, body, image_url, tags, sources, status, +review_status, reviewer, reviewed_at, review_note, topic_slug, +meta_title, meta_description, primary_keyword, secondary_keywords, faq, +internal_links, external_links, reading_time, canonical_url, og_image, +representation, author, city, created_at, updated_at +``` + +اما صفحهٔ بلاگ فقط `title`، `author`، `created`، `tag[0]` (در بردکرامب)، `city` و `body` را رندر می‌کند. + +## مشکل / هدف + +### مشکل ۱ — فیلدهایی که رندر نمی‌شوند + +| فیلد بک‌اند | وضعیت فعلی در سایت | +|---|---| +| `summary` (خلاصه) | ❌ فقط در متادیتا؛ در HTML صفحه هیچ‌جا نیست | +| `tags` | ⚠️ فقط تگ اول در بردکرامب (`components/blog/head/index.js:5`)؛ فهرست تگ‌ها نیست | +| `reading_time` | ❌ رندر نمی‌شود | +| `sources` (منابع، سیگنال E-E-A-T) | ❌ رندر نمی‌شود | +| `faq` | ⚠️ فقط داخل JSON-LD (`app/blog/[slug]/page.js:173`)؛ در HTML قابل‌مشاهده نیست — گوگل برای rich result نیازمند حضور مرئی همان پرسش/پاسخ‌هاست، پس FAQPage فعلی در معرض بی‌اعتبار شدن است | +| `internal_links` / `external_links` | ❌ رندر نمی‌شود | +| `status` | ✅ درست: پستِ غیرمنتشر از بک‌اند ۴۰۴ می‌گیرد | +| `slug` / `meta_*` / `canonical_url` / `og_image` | ✅ در `generateMetadata` مصرف می‌شود | + +**نکتهٔ خلاف درخواست کاربر:** بلاگ در بک‌اند **دسته‌بندی (category) ندارد**؛ فقط ستون json `tags`. پس «دسته‌بندی‌ها» با نمایش تگ‌ها پوشش داده می‌شود. ساخت Entity دسته‌بندی تسک جدا و migration جدا می‌خواهد و در محدودهٔ این پرامپت نیست. + +### مشکل ۲ — تصویر پیش‌فرض + +سه fallback ناهماهنگ و نامناسب در کد وجود دارد و در صفحهٔ جزئیات اصلاً fallback ای نیست: + +`components/blog/detail/Caption.js:7,10` +```jsx + const cover = data?.images?.[0]?.url ? imageUrl(data.images[0].url) : ""; + ... + {cover && cover.trim() !== "" && ( +``` +→ مقالهٔ بدون تصویر **هیچ تصویری** ندارد. + +`components/blogs/latestArticles/Article.js:8-10` +```jsx + const imageSrc = data.images?.[0]?.url + ? imageUrl(data.images[0].url, "/assets/images/cover-blog-1.png") + : "/assets/images/cover-blog-1.png"; +``` + +`components/blog/relatedContent/Item.js:8-10` +```jsx + const cover = data?.images?.[0]?.url + ? imageUrl(data.images[0].url) + : "/assets/images/cover-blog-1.png"; +``` + +`app/blog/[slug]/page.js:16` +```js +const FALLBACK_IMG = "/assets/images/og-image.png"; +``` + +و پیش‌فرضِ خودِ هلپر یک آواتار انسان است (`helper/index.js`): +```js +export const imageUrl = (url, fallback = "/assets/images/user.png") => { +``` +یعنی هر جای دیگری که `imageUrl(blog.image_url)` بدون آرگومان دوم صدا زده شود، عکسِ «کاربر» را برای مقاله نشان می‌دهد. + +### مشکل ۳ — تأخیر تا یک ساعت در نمایش تغییرات ادمین + +`app/blog/[slug]/page.js:21-32` +```js +const getBlog = cache(async (slug, cityId) => { + const query = cityId != null ? `?city_id=${cityId}` : ""; + const res = await fetch(`${API_URL}/api/v1/blog/${slug}${query}`, { + next: { revalidate: 3600, tags: [`blog-${slug}`] }, + }); +``` +tag تعریف شده ولی **هیچ‌کس آن را باطل نمی‌کند** (`app/api/` فقط `auth` دارد). پس تغییر ادمین تا ۶۰ دقیقه دیده نمی‌شود. + +### مشکل ۴ — استایل محتوای CKEditor حذف می‌شود + +`lib/sanitize.js` تگ‌های `figure`/`figcaption` را در `ALLOWED_TAGS` ندارد و `class`/`style` را هم در `ALLOWED_ATTR` ندارد. خروجی جدول/تصویر CKEditor به شکل `
` است؛ DOMPurify با `KEEP_CONTENT` پیش‌فرض، محتوای داخلی را نگه می‌دارد ولی wrapper و کلاس‌ها را حذف می‌کند → جدول و تصویرِ داخل متن بدون هیچ استایلی و چسبیده رندر می‌شوند. (این حذف داده نیست، حذف ساختار/استایل است — واقعیت را همین‌طور گزارش کن.) + +## معیار پذیرش + +- ✅ **موفق:** + - در `/blog/` یک مقالهٔ کامل: خلاصه، فهرست تگ‌ها، زمان مطالعه، تصویر شاخص، متن، بخش «سؤالات متداول» مرئی، بخش «منابع» با لینک‌های `sources` — همه رندر می‌شوند و مقادیرشان دقیقاً با پاسخ `GET /api/v1/blog/{slug}` یکی است. + - مقالهٔ بدون `image_url` در صفحهٔ جزئیات، کارت‌های لیست، مقالات مرتبط و OG/Twitter همگی **همان یک** تصویر پیش‌فرض برنددار را نشان می‌دهند. + - `POST /api/revalidate` با هدر صحیح → `200 {"revalidated":true}` و بلافاصله پس از آن، صفحهٔ بلاگ محتوای جدید را نشان می‌دهد (بدون انتظار یک‌ساعته). + - `npm run build` و `npm run lint` سبز. +- ❌ **خطا:** + - `POST /api/revalidate` بدون هدر یا با سکرت غلط → `401` و هیچ باطل‌سازی‌ای انجام نشود. + - `POST /api/revalidate` با بدنهٔ نامعتبر (بدون `tags` یا `tags` غیرآرایه) → `400`. + - اسلاگ ناموجود → همان `notFound()` فعلی (۴۰۴)، بدون خطای رندر. +- ⚠️ **مرزی:** + - مقاله‌ای با `faq: []` و `sources: []` و `tags: []` → هیچ سکشن خالی یا هدینگ بی‌محتوا رندر نشود (نه «سؤالات متداول» خالی، نه `