# ساده‌سازی «تنظیمات پیامک (SMS)» — فقط API Key از env ## پروژه `clinicpro` (Backend Symfony + پنل ادمین React) ## زمینه در پنل ادمین (`https://clinic-pro.ddev.site/admin` → صفحه تنظیمات) بخش **«تنظیمات پیامک (SMS)»** الان چهار مورد قابل‌ویرایش دارد که همه در جدول DB `site_config` ذخیره می‌شوند: - `sms_provider` (انتخاب سرویس: کاوه‌نگار / رنگینه) - `sms_price_rials` (قیمت هر پیامک) - `kavenegar_api_key` (کلید API) - `kavenegar_sender` (شماره فرستنده) مقدار API Key عملاً باید یک راز (secret) باشد و جای آن env است، نه دیتابیس یا فرم قابل‌ویرایش پنل. تصمیم نهایی: - **تنها `KAVENEGAR_API_KEY` در env بماند.** بقیه env های SMS حذف شوند. - در پنل، کلید فقط **read-only** نمایش داده شود (وضعیت «تنظیم‌شده / تنظیم‌نشده») و از env خوانده شود؛ دیگر در DB ذخیره نشود. - سه مورد دیگر (provider، price، sender) از **UI و Backend به‌طور کامل حذف** شوند. - خودِ کارت/بخش «تنظیمات پیامک (SMS)» باقی بماند. ## هدف 1. API Key فقط از env (`KAVENEGAR_API_KEY`) — read-only در پنل، بدون ذخیره در DB. 2. حذف کامل `sms_provider`، `sms_price_rials`، `kavenegar_sender` (و کلیدهای rangineh) از UI و Backend config. 3. Provider همیشه `kavenegar`. Sender خالی → کاوه‌نگار از خط پیش‌فرض حساب استفاده می‌کند. 4. `sms_price_rials` که قبلاً از config خوانده می‌شد، تبدیل به ثابت (constant) شود تا کیف‌پول پیامک نشکند. 5. همه فایل‌های env به‌روزرسانی شوند: فقط `KAVENEGAR_API_KEY` بماند. ## فایل‌های مرتبط | فایل | نقش | تغییر | |------|-----|-------| | `assets/admin/pages/SettingsPage.tsx` | فرم تنظیمات پنل | حذف ۳ فیلد، تبدیل API Key به read-only status | | `assets/admin/types/index.ts` | تایپ‌ها | حذف `sms_price_rials` | | `src/Config/Controller/SiteConfigController.php` | endpoint `/api/v1/admin/settings` | حذف کلیدهای SMS از `ALLOWED_KEYS`؛ افزودن flag read-only | | `src/Config/Repository/SiteConfigRepository.php` | مقادیر پیش‌فرض config | حذف کلیدهای SMS از `DEFAULTS` | | `src/Sms/Provider/KavehNegarProvider.php` | provider کاوه‌نگار | خواندن key/sender فقط از env، نه configRepo | | `src/Sms/Controller/SmsWalletController.php` | balance/price | `sms_price_rials` از ثابت به‌جای config | | `config/services.yaml` | wiring provider | تنظیم binding های env | | `.env` و بقیه `.env.*` | متغیرهای محیطی | فقط `KAVENEGAR_API_KEY` | | `docs/api/sms.md` | مستندات | ذکر منبع جدید API Key | --- ## وضعیت فعلی (کد واقعی) ### ۱. فرم پنل — `assets/admin/pages/SettingsPage.tsx` Zod schema (خطوط ۴۰–۴۳): ```tsx sms_provider: z.string(), kavenegar_api_key: z.string(), kavenegar_sender: z.string(), sms_price_rials: z.string(), ``` interface (خطوط ۶۵–۶۸) + reset defaults (۱۲۳–۱۲۶) + reset onClick (۵۴۴–۵۴۷) هم همین ۴ کلید را دارند. بخش UI (خطوط ۴۷۱–۵۲۰): ```tsx {/* تنظیمات پیامک */}
{/* ... هدر «تنظیمات پیامک (SMS)» ... */}
تنظیمات کاوه‌نگار
{/* دکمه نمایش/پنهان */}
``` ### ۲. `src/Config/Repository/SiteConfigRepository.php` — `DEFAULTS` (خطوط ۳۲–۳۷) ```php // sms provider 'sms_provider' => 'kavenegar', 'kavenegar_api_key' => '', 'kavenegar_sender' => '', 'rangineh_api_key' => '', 'rangineh_sender' => '', 'sms_price_rials' => '500', ``` ### ۳. `src/Config/Controller/SiteConfigController.php` — `ALLOWED_KEYS` (خطوط ۳۸–۴۴) ```php // sms provider 'sms_provider', 'kavenegar_api_key', 'kavenegar_sender', 'rangineh_api_key', 'rangineh_sender', 'sms_price_rials', ``` ### ۴. `src/Sms/Provider/KavehNegarProvider.php` (خطوط ۱۳–۲۴) ```php public function __construct( private readonly HttpClientInterface $httpClient, private readonly SiteConfigRepository $configRepo, private readonly LoggerInterface $logger, private readonly ?string $apiKey = null, private readonly ?string $sender = null, ) {} private function key(): string { return $this->configRepo->get('kavenegar_api_key') ?: ($this->apiKey ?? ''); } private function sender(): string { return $this->configRepo->get('kavenegar_sender') ?: ($this->sender ?? ''); } ``` ### ۵. `src/Sms/Controller/SmsWalletController.php` (خط ۵۶) ```php $smsPriceRials = (int) ($this->configRepo->get('sms_price_rials') ?? 500); ``` ### ۶. `config/services.yaml` (خطوط ۹۲–۹۹) ```yaml App\Sms\Provider\KavehNegarProvider: arguments: $apiKey: '%env(default::KAVENEGAR_API_KEY)%' $sender: '%env(default::KAVENEGAR_SENDER)%' App\Sms\Provider\RanginehProvider: arguments: $apiKey: '%env(default::RANGINEH_API_KEY)%' $sender: '%env(default::RANGINEH_SENDER)%' ``` ### ۷. env فعلی — `.env` (خطوط ۳۸–۴۲ و ۶۰–۶۴) ``` ###> SMS ### KAVENEGAR_API_KEY=change_me RANGINEH_API_KEY=change_me SMS_PROVIDER=kavenegar ###< SMS ### ... # SMS KAVENEGAR_API_KEY=test_key KAVENEGAR_SENDER=1000596446 RANGINEH_API_KEY=test_key RANGINEH_SENDER=3000 ``` > توجه: `.env` هم بلوک `###> SMS ###` دارد هم یک بخش دوم پایین فایل. هر دو باید یکی شوند. --- ## وظایف ### ۱. Backend — provider کاوه‌نگار فقط از env در `src/Sms/Provider/KavehNegarProvider.php`: - متد `key()` و `sender()` دیگر از `configRepo` نخوانند؛ فقط از env (constructor): ```php private function key(): string { return $this->apiKey ?? ''; } private function sender(): string { return $this->sender ?? ''; } ``` - چون `SiteConfigRepository` دیگر در این کلاس استفاده نمی‌شود، `use App\Config\Repository\SiteConfigRepository;` و پارامتر `private readonly SiteConfigRepository $configRepo,` از constructor حذف شود. - کامنت خطوط ۱۷–۱۸ («the real key normally comes from DB site config») هم اصلاح شود که حالا فقط از env می‌آید. ### ۲. Backend — حذف کلیدهای SMS از config - `src/Config/Repository/SiteConfigRepository.php`: کل بلوک `// sms provider` (۶ کلید: `sms_provider`, `kavenegar_api_key`, `kavenegar_sender`, `rangineh_api_key`, `rangineh_sender`, `sms_price_rials`) از `DEFAULTS` حذف شود. - `src/Config/Controller/SiteConfigController.php`: همان ۶ کلید از `ALLOWED_KEYS` حذف شوند (دیگر از طریق PATCH قابل ذخیره نیستند). ### ۳. Backend — قیمت پیامک به ثابت در `src/Sms/Controller/SmsWalletController.php`، چون `sms_price_rials` دیگر در config نیست، آن را ثابت کن: ```php class SmsWalletController extends BaseController { private const SMS_PRICE_RIALS = 500; ... // به‌جای config: $smsPriceRials = self::SMS_PRICE_RIALS; ``` > `SmsWalletPage.tsx` (خط ۱۲۶) مقدار `balance.sms_price_rials` را از پاسخ همین endpoint می‌خواند؛ چون همچنان در پاسخ برگردانده می‌شود، نیازی به تغییر ندارد. ### ۴. Backend — نمایش وضعیت read-only کلید در پنل چون API Key دیگر در DB نیست، برای اینکه پنل بتواند «تنظیم‌شده/نشده» را نشان دهد، در `SiteConfigController::get()` یک flag محاسبه‌شده اضافه کن (بدون افشای خودِ کلید): ```php #[Route('/api/v1/admin/settings', methods: ['GET'])] public function get(): JsonResponse { $config = $this->configRepo->getAll(); // API Key پیامک فقط از env؛ فقط وضعیت تنظیم‌بودن برگردانده می‌شود نه مقدار. $config['sms_api_key_configured'] = ($_ENV['KAVENEGAR_API_KEY'] ?? '') !== ''; return $this->success($config); } ``` > اگر پروژه برای خواندن env از سرویس/پارامتر خاصی استفاده می‌کند، از همان الگو استفاده کن؛ اگر نه، خواندن مستقیم از `$_ENV` قابل‌قبول است. ### ۵. `config/services.yaml` - برای `KavehNegarProvider` فقط `$apiKey` از env بماند؛ خط `$sender` حذف شود (constructor مقدار پیش‌فرض `null` دارد → خط پیش‌فرض کاوه‌نگار): ```yaml App\Sms\Provider\KavehNegarProvider: arguments: $apiKey: '%env(default::KAVENEGAR_API_KEY)%' ``` - binding های `RanginehProvider` (خطوط ۹۶–۹۹) که به env حذف‌شده اشاره می‌کنند حذف شوند. کلاس `RanginehProvider` و تزریق آن در `SmsService` دست‌نخورده بماند (فقط دیگر انتخاب نمی‌شود؛ `resolveProvider()` به‌صورت پیش‌فرض `kavenegar` برمی‌گرداند). ### ۶. Frontend — `assets/admin/pages/SettingsPage.tsx` - از Zod schema (۴۰–۴۳): حذف `sms_provider`، `kavenegar_sender`، `sms_price_rials`. کلید `kavenegar_api_key` هم چون دیگر ارسال نمی‌شود از schema حذف شود. - از interface تنظیمات (۶۵–۶۸)، reset defaults (۱۲۳–۱۲۶) و reset onClick (۵۴۴–۵۴۷): همان کلیدها حذف شوند. - state مربوط به `showKavenegarKey` حذف شود (دیگر input رمزی نداریم). - کارت «تنظیمات پیامک (SMS)» بماند اما محتوایش به یک نمایش read-only تبدیل شود که وضعیت را از `settings.sms_api_key_configured` می‌خواند: ```tsx {/* تنظیمات پیامک */}
📱

تنظیمات پیامک (SMS)

{settings?.sms_api_key_configured ? ✓ تنظیم‌شده (از متغیر محیطی KAVENEGAR_API_KEY) : ✗ تنظیم‌نشده — مقدار KAVENEGAR_API_KEY را در فایل env قرار دهید}

کلید API فقط از طریق متغیر محیطی سرور مدیریت می‌شود و در این پنل قابل ویرایش نیست.

``` - `assets/admin/types/index.ts` خط ۴۱۵: `sms_price_rials: number;` حذف شود. اگر تایپ تنظیمات، `sms_provider`/`kavenegar_api_key`/`kavenegar_sender` هم دارد، آن‌ها هم حذف و در صورت لزوم `sms_api_key_configured: boolean;` اضافه شود. ### ۷. به‌روزرسانی همه فایل‌های env در **همه** این فایل‌ها فقط `KAVENEGAR_API_KEY` بماند و `KAVENEGAR_SENDER`، `RANGINEH_API_KEY`، `RANGINEH_SENDER`، `SMS_PROVIDER` حذف شوند: `.env`, `.env.example`, `.env.dev`, `.env.local`, `.env.test`, `.env.coolify.example`, `.env.liara.example` الگوی نهایی بلوک SMS (در `.env`): ``` ###> SMS ### KAVENEGAR_API_KEY=change_me ###< SMS ### ``` - در `.env` بخش تکراری پایین فایل (خطوط ۶۰–۶۴) هم پاک‌سازی شود؛ فقط یک `KAVENEGAR_API_KEY` باقی بماند. - در `.env.example` کامنت خط ۴۱ اصلاح شود به این مضمون: «کلید کاوه‌نگار فقط از env خوانده می‌شود» (نه از DB). - برای فایل‌های نمونه (`*.example`) مقدار placeholder مثل `KAVENEGAR_API_KEY=` یا `change_me` بگذار؛ در `.env.test` مقدار تستی (مثلاً `test_key`). ### ۸. مستندات — `docs/api/sms.md` - ذکر شود API Key کاوه‌نگار فقط از env `KAVENEGAR_API_KEY` خوانده می‌شود. - ذکر شود endpoint `/api/v1/admin/settings` دیگر کلیدهای `sms_provider`، `kavenegar_api_key`، `kavenegar_sender`، `rangineh_*`، `sms_price_rials` را نمی‌پذیرد؛ در پاسخ GET فیلد read-only `sms_api_key_configured` اضافه شده. - provider ثابت `kavenegar` و قیمت پیامک ثابت `SMS_PRICE_RIALS = 500` مستند شود. --- ## نکات مهم - **پاک‌سازی DB (اختیاری):** ردیف‌های قدیمی این کلیدها ممکن است در جدول `site_config` مانده باشند. حذفشان ضروری نیست (دیگر خوانده نمی‌شوند) اما برای تمیزی می‌توان با یک کوئری آن‌ها را حذف کرد. migration لازم نیست چون Entity تغییر نکرده. - **Sender خالی:** با حذف `kavenegar_sender`، متد `send()` مقدار `sender=''` می‌فرستد؛ کاوه‌نگار در این حالت از خط پیش‌فرض حساب استفاده می‌کند. اگر خط اختصاصی لازم شد، بعداً می‌توان `KAVENEGAR_SENDER` را به env و binding برگرداند — طبق درخواست فعلی فقط API Key در env می‌ماند. - **سازگاری پاسخ:** ساختار پاسخ‌ها با `$this->success()` حفظ شود (BaseController). - **بررسی تایپ فرانت:** بعد از تغییر، `ddev exec npx tsc --noEmit --project tsconfig.json` خطا ندهد (کلیدهای حذف‌شده جایی رفرنس نشده باشند — خصوصاً `showKavenegarKey`). - **بیلد:** `ddev exec yarn dev` برای تأیید بیلد JS/TS. - **پاک کردن کش بعد از تغییر services.yaml/env:** `ddev exec php bin/console cache:clear`. - **graphify:** بعد از پایان تغییرات `graphify update .` اجرا شود.