Files
clinicpro/.claude/prompt/sms-settings-apikey-env-only.md

16 KiB
Raw Permalink Blame History

ساده‌سازی «تنظیمات پیامک (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 (خطوط ۴۰–۴۳):

  sms_provider:       z.string(),
  kavenegar_api_key:  z.string(),
  kavenegar_sender:   z.string(),
  sms_price_rials:    z.string(),

interface (خطوط ۶۵–۶۸) + reset defaults (۱۲۳–۱۲۶) + reset onClick (۵۴۴–۵۴۷) هم همین ۴ کلید را دارند.

بخش UI (خطوط ۴۷۱–۵۲۰):

          {/* تنظیمات پیامک */}
          <div className="card card-pad">
            {/* ... هدر «تنظیمات پیامک (SMS)» ... */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
              <div>
                <label>سرویس پیامک</label>
                <select {...register('sms_provider')}>
                  <option value="kavenegar">کاوه‌نگار</option>
                  <option value="rangineh">رنگینه</option>
                </select>
              </div>
              <div>
                <label>قیمت هر پیامک (ریال)</label>
                <input {...register('sms_price_rials')} type="number" ... />
              </div>
            </div>
            <div style={{ marginTop: '1rem' }}>
              <div>تنظیمات کاوه‌نگار</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                <div>
                  <label>API Key</label>
                  <input {...register('kavenegar_api_key')} type={showKavenegarKey ? 'text' : 'password'} ... />
                  {/* دکمه نمایش/پنهان */}
                </div>
                <div>
                  <label>شماره فرستنده</label>
                  <input {...register('kavenegar_sender')} ... />
                </div>
              </div>
            </div>
          </div>

۲. src/Config/Repository/SiteConfigRepository.phpDEFAULTS (خطوط ۳۲–۳۷)

        // sms provider
        'sms_provider'               => 'kavenegar',
        'kavenegar_api_key'          => '',
        'kavenegar_sender'           => '',
        'rangineh_api_key'           => '',
        'rangineh_sender'            => '',
        'sms_price_rials'            => '500',

۳. src/Config/Controller/SiteConfigController.phpALLOWED_KEYS (خطوط ۳۸–۴۴)

        // sms provider
        'sms_provider',
        'kavenegar_api_key',
        'kavenegar_sender',
        'rangineh_api_key',
        'rangineh_sender',
        'sms_price_rials',

۴. src/Sms/Provider/KavehNegarProvider.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 (خط ۵۶)

        $smsPriceRials = (int) ($this->configRepo->get('sms_price_rials') ?? 500);

۶. config/services.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):
    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 نیست، آن را ثابت کن:

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 محاسبه‌شده اضافه کن (بدون افشای خودِ کلید):

    #[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 دارد → خط پیش‌فرض کاوه‌نگار):
    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 می‌خواند:
          {/* تنظیمات پیامک */}
          <div className="card card-pad">
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.25rem' }}>
              <div className="ico" style={{ background: 'var(--violet-bg)', color: 'var(--violet)', width: 36, height: 36, borderRadius: 10 }}>
                <span style={{ fontSize: 16 }}>📱</span>
              </div>
              <h3 style={{ fontSize: 15 }}>تنظیمات پیامک (SMS)</h3>
            </div>

            <div>
              <label style={{ fontSize: 12.5, fontWeight: 500, display: 'block', marginBottom: 4 }}>
                کلید API کاوه‌نگار
              </label>
              <div style={{ fontSize: 13, color: 'var(--muted)' }}>
                {settings?.sms_api_key_configured
                  ? <span style={{ color: 'var(--success)' }}> تنظیم‌شده (از متغیر محیطی KAVENEGAR_API_KEY)</span>
                  : <span style={{ color: 'var(--danger)' }}> تنظیم‌نشده  مقدار KAVENEGAR_API_KEY را در فایل env قرار دهید</span>}
              </div>
              <p style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 8 }}>
                کلید API فقط از طریق متغیر محیطی سرور مدیریت می‌شود و در این پنل قابل ویرایش نیست.
              </p>
            </div>
          </div>
  • 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 . اجرا شود.