# Permission Catalog API > **Prefix:** `/api/v1/permission-catalog` ## چرا این اندپوینت هست فهرستِ منابعِ قابل‌مجوزدهی تا پیش از این شش جا تکرار شده بود — دو Entity، سه فایل UI و یک interface تایپ‌اسکریپت — و از هم واگرا شده بودند. نتیجه‌اش این بود که صفحهٔ تازه به‌جای منبعِ خودش، مجوزِ نزدیک‌ترین صفحهٔ موجود را قرض می‌گرفت. حالا `App\Shared\Security\PermissionCatalog` تنها جایی است که می‌گوید چه منابع و اکشن‌هایی وجود دارند. این اندپوینت همان رجیستری را به پنل می‌دهد تا هر دو فرمِ مجوز — منشی و پزشکِ عضو کلینیک — به‌جای فهرستِ هاردکد از آن رندر شوند. **افزودن صفحهٔ تازه = یک ردیف در `PermissionCatalog::RESOURCES`.** فرانت تغییری لازم ندارد. رجیستری فقط **ساختار** می‌دهد. مقدارِ پیش‌فرضِ هر نقش سیاست است و در Entity خودش می‌ماند: `DoctorSecretary::DEFAULT_PERMISSIONS` و `ClinicDoctorPermission::DEFAULT_PERMISSIONS`. --- ## GET `/api/v1/permission-catalog` فهرستِ کاملِ منابع و اکشن‌ها با برچسب فارسی. **Permission:** `IS_AUTHENTICATED_FULLY` پاسخ به کاربر بستگی ندارد و تا وقتی رجیستری عوض نشود ثابت است، پس کلاینت می‌تواند بلندمدت cache کند (`staleTime: Infinity`). مقدارِ واقعیِ مجوزِ هر رابطه از اندپوینت‌های خودِ منشی/پزشک می‌آید، نه از اینجا. `resources` آرایه است نه object، تا ترتیبِ نمایش بخشی از قرارداد باشد؛ ترتیبِ کلیدهای JSON قرارداد نیست. ### Response `200` (خروجی واقعی، بریده‌شده) ```json { "success": true, "data": { "version": 1, "resources": [ { "key": "appointments", "label": "مدیریت نوبت‌ها", "clinic_only": false, "actions": [ { "key": "view", "label": "مشاهده نوبت‌ها" }, { "key": "create", "label": "ایجاد نوبت" }, { "key": "cancel", "label": "لغو نوبت" }, { "key": "update_status", "label": "تغییر وضعیت نوبت" } ] }, { "key": "clinic_doctors", "label": "مدیریت پزشکان کلینیک", "clinic_only": true, "actions": [ { "key": "view", "label": "مشاهده پزشکان" }, { "key": "create", "label": "افزودن پزشک" }, { "key": "update", "label": "ویرایش پزشک" }, { "key": "delete", "label": "حذف پزشک" } ] } ] } } ``` | فیلد | نوع | توضیح | | --- | --- | --- | | `version` | int | نسخهٔ شِمای مجوزها. همان چیزی که در envelope ذخیره می‌شود | | `resources[].key` | string | کلیدِ منبع — همان چیزی که در `permission` JSON می‌نشیند | | `resources[].label` | string | برچسب فارسی برای نمایش | | `resources[].clinic_only` | bool | فقط در محیطِ کلینیک معنا دارد؛ پزشکِ مستقل نباید ببیندش | | `resources[].actions[].key` | string | `view` / `create` / `update` / `delete` / `cancel` / `update_status` | | `resources[].actions[].label` | string | برچسب فارسیِ همان اکشن | ### منابعِ فعلی (۱۷) `appointments` · `patients` · `treatment` · `payments` · `insurances` · `addresses` · `clinic_info` · `services` · `inventory` · `staff` · `tags` · `discounts` · `sms` · `appointment_settings` · `resources` · `clinic_doctors` · `subscription` اکشن‌ها یکسان نیستند: `subscription` فقط `view/create` دارد، `clinic_info` / `appointment_settings` / `treatment` فقط `view/update`، و `addresses` **فقط `view`**. منبعِ `addresses` عمداً `create/update/delete` ندارد: ساخت و ویرایش و حذفِ آدرس در `ClinicController` صریحاً owner-or-admin است (`$clinic->getUser()->getId() !== $user->getId()`) و قابل واگذاری به منشی یا پزشکِ عضو نیست. تا پیش از این هر سهٔ آن توگل‌ها در فرمِ مالک بودند و هیچ چیزی را کنترل نمی‌کردند. ### Errors | Status | شرایط | | --- | --- | | `401` | بدون توکن یا توکنِ نامعتبر | --- ## سازگاری با دادهٔ موجود `getPermissions()` روی هر دو Entity، JSONِ ذخیره‌شده را روی پیش‌فرضِ نقش merge می‌کند: - منبعی که در رجیستری هست و در JSONِ ردیف نیست (یعنی بعد از ساختِ آن ردیف اضافه شده) **پیش‌فرضِ نقش** را می‌گیرد، نه `false`. بدون این، هر منبع تازه برای همهٔ ردیف‌های موجود خاموش می‌ماند. - مقدارِ صریحِ ذخیره‌شده هرگز بازنویسی نمی‌شود — حتی `false`. - هیچ migration دادهٔ انبوهی لازم نیست؛ ستون `permission` از نوع `json` است و schema عوض نمی‌شود. نوشتن‌ها از `PermissionCatalog::filterPatch()` عبور می‌کنند: منبع یا اکشنِ خارج از رجیستری **بی‌صدا** کنار گذاشته می‌شود (نه `422`) — همان رفتاری که کلاینت‌های فعلی روی آن حساب کرده‌اند. بقیهٔ کلیدهای همان درخواست اعمال می‌شوند. مصرف‌کننده‌ها: [secretary.md](secretary.md) · [clinic.md](clinic.md)