16 KiB
سادهسازی «تنظیمات پیامک (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)» باقی بماند.
هدف
- API Key فقط از env (
KAVENEGAR_API_KEY) — read-only در پنل، بدون ذخیره در DB. - حذف کامل
sms_provider،sms_price_rials،kavenegar_sender(و کلیدهای rangineh) از UI و Backend config. - Provider همیشه
kavenegar. Sender خالی → کاوهنگار از خط پیشفرض حساب استفاده میکند. sms_price_rialsکه قبلاً از config خوانده میشد، تبدیل به ثابت (constant) شود تا کیفپول پیامک نشکند.- همه فایلهای 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.php — DEFAULTS (خطوط ۳۲–۳۷)
// 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 (خطوط ۳۸–۴۴)
// 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-onlysms_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 .اجرا شود.